From 686106d544ecc3b6ffd4db2b665d3bc879a58d8c Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 24 Sep 2012 16:22:07 -0400 Subject: ok --- node_modules/ejs/.gitmodules | 0 node_modules/ejs/.npmignore | 4 + node_modules/ejs/History.md | 98 + node_modules/ejs/Makefile | 23 + node_modules/ejs/Readme.md | 151 + node_modules/ejs/benchmark.js | 14 + node_modules/ejs/ejs.js | 567 + node_modules/ejs/ejs.min.js | 2 + node_modules/ejs/examples/client.html | 24 + node_modules/ejs/examples/list.ejs | 7 + node_modules/ejs/examples/list.js | 14 + node_modules/ejs/index.js | 2 + node_modules/ejs/lib/ejs.js | 298 + node_modules/ejs/lib/filters.js | 198 + node_modules/ejs/lib/utils.js | 23 + node_modules/ejs/package.json | 32 + node_modules/ejs/support/compile.js | 174 + node_modules/ejs/test/ejs.test.js | 329 + node_modules/ejs/test/fixtures/user.ejs | 1 + node_modules/express/.npmignore | 7 + node_modules/express/History.md | 805 + node_modules/express/LICENSE | 22 + node_modules/express/Makefile | 29 + node_modules/express/Readme.md | 145 + node_modules/express/bin/express | 416 + node_modules/express/index.js | 2 + node_modules/express/lib/express.js | 79 + node_modules/express/lib/http.js | 582 + node_modules/express/lib/https.js | 52 + node_modules/express/lib/request.js | 323 + node_modules/express/lib/response.js | 460 + node_modules/express/lib/router/collection.js | 53 + node_modules/express/lib/router/index.js | 398 + node_modules/express/lib/router/methods.js | 70 + node_modules/express/lib/router/route.js | 88 + node_modules/express/lib/utils.js | 152 + node_modules/express/lib/view.js | 460 + node_modules/express/lib/view/partial.js | 40 + node_modules/express/lib/view/view.js | 210 + .../express/node_modules/connect/.npmignore | 11 + node_modules/express/node_modules/connect/LICENSE | 24 + node_modules/express/node_modules/connect/index.js | 2 + .../express/node_modules/connect/lib/cache.js | 81 + .../express/node_modules/connect/lib/connect.js | 106 + .../express/node_modules/connect/lib/http.js | 217 + .../express/node_modules/connect/lib/https.js | 47 + .../express/node_modules/connect/lib/index.js | 46 + .../connect/lib/middleware/basicAuth.js | 93 + .../connect/lib/middleware/bodyParser.js | 196 + .../connect/lib/middleware/compiler.js | 163 + .../connect/lib/middleware/cookieParser.js | 46 + .../node_modules/connect/lib/middleware/csrf.js | 105 + .../connect/lib/middleware/directory.js | 222 + .../connect/lib/middleware/errorHandler.js | 100 + .../node_modules/connect/lib/middleware/favicon.js | 76 + .../node_modules/connect/lib/middleware/limit.js | 80 + .../node_modules/connect/lib/middleware/logger.js | 299 + .../connect/lib/middleware/methodOverride.js | 38 + .../connect/lib/middleware/profiler.js | 100 + .../node_modules/connect/lib/middleware/query.js | 40 + .../connect/lib/middleware/responseTime.js | 34 + .../node_modules/connect/lib/middleware/router.js | 379 + .../node_modules/connect/lib/middleware/session.js | 345 + .../connect/lib/middleware/session/cookie.js | 126 + .../connect/lib/middleware/session/memory.js | 131 + .../connect/lib/middleware/session/session.js | 137 + .../connect/lib/middleware/session/store.js | 87 + .../node_modules/connect/lib/middleware/static.js | 225 + .../connect/lib/middleware/staticCache.js | 175 + .../node_modules/connect/lib/middleware/vhost.js | 44 + .../express/node_modules/connect/lib/patch.js | 79 + .../node_modules/connect/lib/public/directory.html | 75 + .../node_modules/connect/lib/public/error.html | 13 + .../node_modules/connect/lib/public/favicon.ico | Bin 0 -> 1406 bytes .../node_modules/connect/lib/public/style.css | 141 + .../express/node_modules/connect/lib/utils.js | 451 + .../connect/node_modules/formidable/.npmignore | 4 + .../connect/node_modules/formidable/.travis.yml | 4 + .../connect/node_modules/formidable/Makefile | 14 + .../connect/node_modules/formidable/Readme.md | 303 + .../connect/node_modules/formidable/TODO | 3 + .../formidable/benchmark/bench-multipart-parser.js | 70 + .../node_modules/formidable/example/post.js | 43 + .../node_modules/formidable/example/upload.js | 48 + .../connect/node_modules/formidable/index.js | 1 + .../connect/node_modules/formidable/lib/file.js | 61 + .../node_modules/formidable/lib/incoming_form.js | 378 + .../connect/node_modules/formidable/lib/index.js | 3 + .../formidable/lib/multipart_parser.js | 312 + .../formidable/lib/querystring_parser.js | 25 + .../connect/node_modules/formidable/lib/util.js | 6 + .../connect/node_modules/formidable/package.json | 32 + .../connect/node_modules/formidable/test/common.js | 19 + .../formidable/test/fixture/file/funkyfilename.txt | 1 + .../formidable/test/fixture/file/plain.txt | 1 + .../fixture/http/special-chars-in-filename/info.md | 3 + .../formidable/test/fixture/js/no-filename.js | 3 + .../test/fixture/js/special-chars-in-filename.js | 21 + .../formidable/test/fixture/multipart.js | 72 + .../formidable/test/integration/test-fixtures.js | 89 + .../node_modules/formidable/test/legacy/common.js | 24 + .../legacy/integration/test-multipart-parser.js | 80 + .../formidable/test/legacy/simple/test-file.js | 104 + .../test/legacy/simple/test-incoming-form.js | 726 + .../test/legacy/simple/test-multipart-parser.js | 50 + .../test/legacy/simple/test-querystring-parser.js | 45 + .../test/legacy/system/test-multi-video-upload.js | 72 + .../connect/node_modules/formidable/test/run.js | 2 + .../formidable/test/unit/test-incoming-form.js | 63 + .../connect/node_modules/formidable/tool/record.js | 47 + .../express/node_modules/connect/package.json | 49 + node_modules/express/node_modules/mime/LICENSE | 19 + node_modules/express/node_modules/mime/README.md | 50 + node_modules/express/node_modules/mime/mime.js | 92 + .../express/node_modules/mime/package.json | 40 + node_modules/express/node_modules/mime/test.js | 79 + .../express/node_modules/mime/types/mime.types | 1479 + .../express/node_modules/mime/types/node.types | 43 + .../express/node_modules/mkdirp/.gitignore.orig | 2 + .../express/node_modules/mkdirp/.gitignore.rej | 5 + .../express/node_modules/mkdirp/.npmignore | 2 + node_modules/express/node_modules/mkdirp/LICENSE | 21 + .../express/node_modules/mkdirp/README.markdown | 54 + .../express/node_modules/mkdirp/examples/pow.js | 6 + .../node_modules/mkdirp/examples/pow.js.orig | 6 + .../node_modules/mkdirp/examples/pow.js.rej | 19 + node_modules/express/node_modules/mkdirp/index.js | 79 + .../express/node_modules/mkdirp/package.json | 40 + .../express/node_modules/mkdirp/test/chmod.js | 38 + .../express/node_modules/mkdirp/test/clobber.js | 37 + .../express/node_modules/mkdirp/test/mkdirp.js | 28 + .../express/node_modules/mkdirp/test/perm.js | 32 + .../express/node_modules/mkdirp/test/perm_sync.js | 39 + .../express/node_modules/mkdirp/test/race.js | 41 + .../express/node_modules/mkdirp/test/rel.js | 32 + .../express/node_modules/mkdirp/test/sync.js | 27 + .../express/node_modules/mkdirp/test/umask.js | 28 + .../express/node_modules/mkdirp/test/umask_sync.js | 27 + node_modules/express/node_modules/qs/.gitmodules | 6 + node_modules/express/node_modules/qs/.npmignore | 1 + node_modules/express/node_modules/qs/.travis.yml | 4 + node_modules/express/node_modules/qs/History.md | 73 + node_modules/express/node_modules/qs/Makefile | 5 + node_modules/express/node_modules/qs/Readme.md | 54 + node_modules/express/node_modules/qs/benchmark.js | 17 + node_modules/express/node_modules/qs/examples.js | 51 + node_modules/express/node_modules/qs/index.js | 2 + .../express/node_modules/qs/lib/querystring.js | 264 + node_modules/express/node_modules/qs/package.json | 33 + .../express/node_modules/qs/test/mocha.opts | 2 + node_modules/express/node_modules/qs/test/parse.js | 167 + .../express/node_modules/qs/test/stringify.js | 103 + node_modules/express/package.json | 74 + node_modules/express/testing/foo/app.js | 35 + node_modules/express/testing/foo/package.json | 9 + .../testing/foo/public/stylesheets/style.css | 8 + node_modules/express/testing/foo/routes/index.js | 10 + node_modules/express/testing/foo/views/index.jade | 2 + node_modules/express/testing/foo/views/layout.jade | 6 + node_modules/express/testing/index.js | 43 + node_modules/express/testing/public/test.txt | 2971 + node_modules/express/testing/views/page.html | 1 + node_modules/express/testing/views/page.jade | 3 + node_modules/express/testing/views/test.md | 1 + node_modules/express/testing/views/user/index.jade | 1 + node_modules/express/testing/views/user/list.jade | 1 + node_modules/hooks/.npmignore | 2 + node_modules/hooks/Makefile | 9 + node_modules/hooks/README.md | 306 + node_modules/hooks/hooks.alt.js | 134 + node_modules/hooks/hooks.js | 161 + node_modules/hooks/package.json | 51 + node_modules/hooks/test.js | 685 + node_modules/mocha/.npmignore | 5 + node_modules/mocha/.travis.yml | 5 + node_modules/mocha/History.md | 317 + node_modules/mocha/LICENSE | 22 + node_modules/mocha/Makefile | 118 + node_modules/mocha/Readme.md | 34 + node_modules/mocha/_mocha.js | 3916 + node_modules/mocha/bin/_mocha | 345 + node_modules/mocha/bin/mocha | 32 + .../Snippets/bdd - after each.tmSnippet | 16 + .../Snippets/bdd - after.tmSnippet | 16 + .../Snippets/bdd - before each.tmSnippet | 16 + .../Snippets/bdd - before.tmSnippet | 16 + .../Snippets/bdd - it.tmSnippet | 16 + .../Snippets/untitled.tmSnippet | 16 + .../editors/JavaScript mocha.tmbundle/info.plist | 19 + node_modules/mocha/images/error.png | Bin 0 -> 1100 bytes node_modules/mocha/images/ok.png | Bin 0 -> 649 bytes node_modules/mocha/index.js | 4 + node_modules/mocha/lib/browser/debug.js | 6 + node_modules/mocha/lib/browser/diff.js | 0 node_modules/mocha/lib/browser/events.js | 178 + node_modules/mocha/lib/browser/fs.js | 0 node_modules/mocha/lib/browser/path.js | 0 node_modules/mocha/lib/browser/progress.js | 125 + node_modules/mocha/lib/browser/tty.js | 8 + node_modules/mocha/lib/context.js | 55 + node_modules/mocha/lib/hook.js | 31 + node_modules/mocha/lib/interfaces/bdd.js | 91 + node_modules/mocha/lib/interfaces/exports.js | 60 + node_modules/mocha/lib/interfaces/index.js | 5 + node_modules/mocha/lib/interfaces/qunit.js | 91 + node_modules/mocha/lib/interfaces/tdd.js | 94 + node_modules/mocha/lib/mocha.js | 176 + node_modules/mocha/lib/reporters/base.js | 319 + node_modules/mocha/lib/reporters/doc.js | 74 + node_modules/mocha/lib/reporters/dot.js | 62 + node_modules/mocha/lib/reporters/html-cov.js | 44 + node_modules/mocha/lib/reporters/html.js | 211 + node_modules/mocha/lib/reporters/index.js | 17 + node_modules/mocha/lib/reporters/json-cov.js | 149 + node_modules/mocha/lib/reporters/json-stream.js | 61 + node_modules/mocha/lib/reporters/json.js | 70 + node_modules/mocha/lib/reporters/landing.js | 97 + node_modules/mocha/lib/reporters/list.js | 64 + node_modules/mocha/lib/reporters/markdown.js | 111 + node_modules/mocha/lib/reporters/min.js | 37 + node_modules/mocha/lib/reporters/progress.js | 85 + node_modules/mocha/lib/reporters/spec.js | 87 + node_modules/mocha/lib/reporters/tap.js | 63 + node_modules/mocha/lib/reporters/teamcity.js | 56 + .../mocha/lib/reporters/templates/coverage.jade | 50 + .../mocha/lib/reporters/templates/menu.jade | 13 + .../mocha/lib/reporters/templates/script.html | 34 + .../mocha/lib/reporters/templates/style.html | 301 + node_modules/mocha/lib/reporters/xunit.js | 101 + node_modules/mocha/lib/runnable.js | 164 + node_modules/mocha/lib/runner.js | 431 + node_modules/mocha/lib/suite.js | 247 + node_modules/mocha/lib/test.js | 49 + node_modules/mocha/lib/utils.js | 189 + node_modules/mocha/mocha.css | 137 + node_modules/mocha/mocha.js | 4067 + node_modules/mocha/node_modules/.bin/jade | 125 + .../mocha/node_modules/commander/.npmignore | 4 + .../mocha/node_modules/commander/.travis.yml | 4 + .../mocha/node_modules/commander/History.md | 99 + node_modules/mocha/node_modules/commander/Makefile | 7 + .../mocha/node_modules/commander/Readme.md | 263 + node_modules/mocha/node_modules/commander/index.js | 2 + .../mocha/node_modules/commander/lib/commander.js | 992 + .../mocha/node_modules/commander/package.json | 38 + node_modules/mocha/node_modules/debug/.npmignore | 4 + node_modules/mocha/node_modules/debug/History.md | 41 + node_modules/mocha/node_modules/debug/Makefile | 5 + node_modules/mocha/node_modules/debug/Readme.md | 130 + node_modules/mocha/node_modules/debug/debug.js | 122 + .../mocha/node_modules/debug/example/app.js | 19 + .../mocha/node_modules/debug/example/browser.html | 24 + .../mocha/node_modules/debug/example/wildcards.js | 10 + .../mocha/node_modules/debug/example/worker.js | 22 + node_modules/mocha/node_modules/debug/index.js | 2 + node_modules/mocha/node_modules/debug/lib/debug.js | 147 + node_modules/mocha/node_modules/debug/package.json | 29 + node_modules/mocha/node_modules/diff/LICENSE | 31 + node_modules/mocha/node_modules/diff/README.md | 94 + node_modules/mocha/node_modules/diff/diff.js | 287 + node_modules/mocha/node_modules/diff/index.html | 89 + node_modules/mocha/node_modules/diff/package.json | 46 + node_modules/mocha/node_modules/diff/style.css | 81 + .../mocha/node_modules/diff/test/diffTest.js | 616 + node_modules/mocha/node_modules/growl/History.md | 42 + node_modules/mocha/node_modules/growl/Readme.md | 93 + node_modules/mocha/node_modules/growl/lib/growl.js | 188 + node_modules/mocha/node_modules/growl/package.json | 22 + node_modules/mocha/node_modules/growl/test.js | 16 + node_modules/mocha/node_modules/jade/.npmignore | 4 + node_modules/mocha/node_modules/jade/LICENSE | 22 + node_modules/mocha/node_modules/jade/bin/jade | 125 + node_modules/mocha/node_modules/jade/index.js | 2 + node_modules/mocha/node_modules/jade/jade.js | 3140 + node_modules/mocha/node_modules/jade/jade.min.js | 2 + .../mocha/node_modules/jade/lib/compiler.js | 503 + .../mocha/node_modules/jade/lib/doctypes.js | 18 + .../mocha/node_modules/jade/lib/filters.js | 97 + .../mocha/node_modules/jade/lib/inline-tags.js | 28 + node_modules/mocha/node_modules/jade/lib/jade.js | 237 + node_modules/mocha/node_modules/jade/lib/lexer.js | 707 + .../node_modules/jade/lib/nodes/block-comment.js | 33 + .../mocha/node_modules/jade/lib/nodes/block.js | 100 + .../mocha/node_modules/jade/lib/nodes/case.js | 43 + .../mocha/node_modules/jade/lib/nodes/code.js | 35 + .../mocha/node_modules/jade/lib/nodes/comment.js | 32 + .../mocha/node_modules/jade/lib/nodes/doctype.js | 29 + .../mocha/node_modules/jade/lib/nodes/each.js | 35 + .../mocha/node_modules/jade/lib/nodes/filter.js | 35 + .../mocha/node_modules/jade/lib/nodes/index.js | 20 + .../mocha/node_modules/jade/lib/nodes/literal.js | 31 + .../mocha/node_modules/jade/lib/nodes/mixin.js | 34 + .../mocha/node_modules/jade/lib/nodes/node.js | 14 + .../mocha/node_modules/jade/lib/nodes/tag.js | 80 + .../mocha/node_modules/jade/lib/nodes/text.js | 42 + node_modules/mocha/node_modules/jade/lib/parser.js | 651 + .../mocha/node_modules/jade/lib/runtime.js | 118 + .../mocha/node_modules/jade/lib/self-closing.js | 18 + node_modules/mocha/node_modules/jade/lib/utils.js | 49 + .../jade/node_modules/mkdirp/.gitignore.orig | 2 + .../jade/node_modules/mkdirp/.gitignore.rej | 5 + .../jade/node_modules/mkdirp/.npmignore | 2 + .../jade/node_modules/mkdirp/.travis.yml | 4 + .../node_modules/jade/node_modules/mkdirp/LICENSE | 21 + .../jade/node_modules/mkdirp/README.markdown | 61 + .../jade/node_modules/mkdirp/examples/pow.js | 6 + .../jade/node_modules/mkdirp/examples/pow.js.orig | 6 + .../jade/node_modules/mkdirp/examples/pow.js.rej | 19 + .../node_modules/jade/node_modules/mkdirp/index.js | 83 + .../jade/node_modules/mkdirp/package.json | 37 + .../jade/node_modules/mkdirp/test/chmod.js | 38 + .../jade/node_modules/mkdirp/test/clobber.js | 37 + .../jade/node_modules/mkdirp/test/mkdirp.js | 28 + .../jade/node_modules/mkdirp/test/perm.js | 32 + .../jade/node_modules/mkdirp/test/perm_sync.js | 39 + .../jade/node_modules/mkdirp/test/race.js | 41 + .../jade/node_modules/mkdirp/test/rel.js | 32 + .../jade/node_modules/mkdirp/test/return.js | 25 + .../jade/node_modules/mkdirp/test/return_sync.js | 24 + .../jade/node_modules/mkdirp/test/sync.js | 32 + .../jade/node_modules/mkdirp/test/umask.js | 28 + .../jade/node_modules/mkdirp/test/umask_sync.js | 32 + node_modules/mocha/node_modules/jade/package.json | 42 + node_modules/mocha/node_modules/jade/runtime.js | 123 + .../mocha/node_modules/jade/runtime.min.js | 1 + node_modules/mocha/package.json | 48 + node_modules/mongodb/.travis.yml | 5 + node_modules/mongodb/Makefile | 71 + node_modules/mongodb/external-libs/bson/Makefile | 45 + node_modules/mongodb/external-libs/bson/bson.cc | 2165 + node_modules/mongodb/external-libs/bson/bson.h | 105 + node_modules/mongodb/external-libs/bson/index.js | 20 + .../mongodb/external-libs/bson/test/test_bson.js | 349 + .../external-libs/bson/test/test_full_bson.js | 218 + .../external-libs/bson/test/test_stackless_bson.js | 132 + node_modules/mongodb/external-libs/bson/wscript | 39 + node_modules/mongodb/index.js | 1 + node_modules/mongodb/install.js | 40 + node_modules/mongodb/lib/mongodb/admin.js | 390 + node_modules/mongodb/lib/mongodb/collection.js | 1504 + .../mongodb/lib/mongodb/commands/base_command.js | 27 + .../mongodb/lib/mongodb/commands/db_command.js | 205 + .../mongodb/lib/mongodb/commands/delete_command.js | 111 + .../lib/mongodb/commands/get_more_command.js | 83 + .../mongodb/lib/mongodb/commands/insert_command.js | 141 + .../lib/mongodb/commands/kill_cursor_command.js | 98 + .../mongodb/lib/mongodb/commands/query_command.js | 209 + .../mongodb/lib/mongodb/commands/update_command.js | 174 + .../mongodb/lib/mongodb/connection/connection.js | 414 + .../lib/mongodb/connection/connection_pool.js | 259 + .../lib/mongodb/connection/connection_utils.js | 23 + .../lib/mongodb/connection/repl_set_servers.js | 972 + .../mongodb/lib/mongodb/connection/server.js | 640 + .../mongodb/connection/strategies/ping_strategy.js | 125 + .../connection/strategies/statistics_strategy.js | 40 + node_modules/mongodb/lib/mongodb/cursor.js | 702 + node_modules/mongodb/lib/mongodb/cursorstream.js | 141 + node_modules/mongodb/lib/mongodb/db.js | 1788 + node_modules/mongodb/lib/mongodb/gridfs/chunk.js | 208 + node_modules/mongodb/lib/mongodb/gridfs/grid.js | 98 + .../mongodb/lib/mongodb/gridfs/gridstore.js | 1109 + .../mongodb/lib/mongodb/gridfs/readstream.js | 179 + node_modules/mongodb/lib/mongodb/index.js | 142 + .../mongodb/lib/mongodb/responses/mongo_reply.js | 131 + node_modules/mongodb/lib/mongodb/utils.js | 74 + node_modules/mongodb/node_modules/bson/.travis.yml | 5 + node_modules/mongodb/node_modules/bson/Makefile | 31 + node_modules/mongodb/node_modules/bson/README | 0 .../mongodb/node_modules/bson/ext/Makefile | 28 + node_modules/mongodb/node_modules/bson/ext/bson.cc | 2165 + node_modules/mongodb/node_modules/bson/ext/bson.h | 105 + .../mongodb/node_modules/bson/ext/index.js | 20 + node_modules/mongodb/node_modules/bson/ext/wscript | 39 + node_modules/mongodb/node_modules/bson/install.js | 41 + .../mongodb/node_modules/bson/lib/bson/binary.js | 336 + .../node_modules/bson/lib/bson/binary_parser.js | 387 + .../mongodb/node_modules/bson/lib/bson/bson.js | 1483 + .../mongodb/node_modules/bson/lib/bson/code.js | 27 + .../mongodb/node_modules/bson/lib/bson/db_ref.js | 33 + .../mongodb/node_modules/bson/lib/bson/double.js | 35 + .../node_modules/bson/lib/bson/float_parser.js | 123 + .../mongodb/node_modules/bson/lib/bson/index.js | 74 + .../mongodb/node_modules/bson/lib/bson/long.js | 856 + .../mongodb/node_modules/bson/lib/bson/max_key.js | 15 + .../mongodb/node_modules/bson/lib/bson/min_key.js | 15 + .../mongodb/node_modules/bson/lib/bson/objectid.js | 251 + .../mongodb/node_modules/bson/lib/bson/symbol.js | 50 + .../node_modules/bson/lib/bson/timestamp.js | 855 + .../mongodb/node_modules/bson/package.json | 58 + .../node_modules/bson/test/browser/bson_test.js | 242 + .../node_modules/bson/test/browser/nodeunit.js | 2034 + .../node_modules/bson/test/browser/suite2.js | 13 + .../node_modules/bson/test/browser/suite3.js | 7 + .../node_modules/bson/test/browser/test.html | 30 + .../node_modules/bson/test/node/bson_array_test.js | 240 + .../bson/test/node/bson_parser_comparision_test.js | 459 + .../node_modules/bson/test/node/bson_test.js | 1591 + .../bson/test/node/bson_typed_array_test.js | 392 + .../bson/test/node/data/test_gs_weird_bug.png | Bin 0 -> 52184 bytes .../node_modules/bson/test/node/test_full_bson.js | 295 + .../node_modules/bson/test/node/tools/utils.js | 80 + .../mongodb/node_modules/bson/tools/gleak.js | 8 + .../bson/tools/jasmine-1.1.0/MIT.LICENSE | 20 + .../bson/tools/jasmine-1.1.0/jasmine-html.js | 190 + .../bson/tools/jasmine-1.1.0/jasmine.css | 166 + .../bson/tools/jasmine-1.1.0/jasmine.js | 2476 + .../bson/tools/jasmine-1.1.0/jasmine_favicon.png | Bin 0 -> 905 bytes node_modules/mongodb/package.json | 214 + node_modules/mongoose/.npmignore | 5 + node_modules/mongoose/.travis.yml | 4 + node_modules/mongoose/History.md | 831 + node_modules/mongoose/Makefile | 25 + node_modules/mongoose/README.md | 356 + node_modules/mongoose/benchmarks/clone.js | 60 + node_modules/mongoose/benchmarks/index.js | 128 + node_modules/mongoose/benchmarks/mem.js | 149 + node_modules/mongoose/docs/defaults.md | 29 + node_modules/mongoose/docs/embedded-documents.md | 79 + node_modules/mongoose/docs/errors.md | 54 + node_modules/mongoose/docs/finding-documents.md | 124 + node_modules/mongoose/docs/getters-setters.md | 52 + node_modules/mongoose/docs/indexes.md | 43 + node_modules/mongoose/docs/methods-statics.md | 55 + node_modules/mongoose/docs/middleware.md | 61 + node_modules/mongoose/docs/migration-guide.md | 148 + node_modules/mongoose/docs/model-definition.md | 142 + node_modules/mongoose/docs/plugins.md | 38 + node_modules/mongoose/docs/populate.md | 166 + node_modules/mongoose/docs/query.md | 362 + node_modules/mongoose/docs/querystream.md | 6 + node_modules/mongoose/docs/schematypes.md | 197 + node_modules/mongoose/docs/validation.md | 92 + node_modules/mongoose/docs/virtuals.md | 84 + node_modules/mongoose/examples/schema.js | 102 + node_modules/mongoose/index.js | 7 + node_modules/mongoose/lib/collection.js | 167 + node_modules/mongoose/lib/connection.js | 517 + node_modules/mongoose/lib/document.js | 1223 + .../lib/drivers/node-mongodb-native/binary.js | 8 + .../lib/drivers/node-mongodb-native/collection.js | 176 + .../lib/drivers/node-mongodb-native/connection.js | 106 + .../lib/drivers/node-mongodb-native/objectid.js | 43 + node_modules/mongoose/lib/error.js | 25 + node_modules/mongoose/lib/index.js | 368 + node_modules/mongoose/lib/model.js | 917 + node_modules/mongoose/lib/namedscope.js | 70 + node_modules/mongoose/lib/promise.js | 145 + node_modules/mongoose/lib/query.js | 1527 + node_modules/mongoose/lib/querystream.js | 179 + node_modules/mongoose/lib/schema.js | 565 + node_modules/mongoose/lib/schema/array.js | 232 + node_modules/mongoose/lib/schema/boolean.js | 59 + node_modules/mongoose/lib/schema/buffer.js | 106 + node_modules/mongoose/lib/schema/date.js | 116 + node_modules/mongoose/lib/schema/documentarray.js | 141 + node_modules/mongoose/lib/schema/index.js | 22 + node_modules/mongoose/lib/schema/mixed.js | 63 + node_modules/mongoose/lib/schema/number.js | 142 + node_modules/mongoose/lib/schema/objectid.js | 126 + node_modules/mongoose/lib/schema/string.js | 180 + node_modules/mongoose/lib/schemadefault.js | 32 + node_modules/mongoose/lib/schematype.js | 428 + node_modules/mongoose/lib/statemachine.js | 179 + node_modules/mongoose/lib/types/array.js | 422 + node_modules/mongoose/lib/types/buffer.js | 171 + node_modules/mongoose/lib/types/documentarray.js | 133 + node_modules/mongoose/lib/types/embedded.js | 103 + node_modules/mongoose/lib/types/index.js | 14 + node_modules/mongoose/lib/types/number.js | 84 + node_modules/mongoose/lib/types/objectid.js | 12 + node_modules/mongoose/lib/utils.js | 434 + node_modules/mongoose/lib/virtualtype.js | 74 + node_modules/mongoose/package.json | 52 + node_modules/mongoose/support/expresso/.gitmodules | 3 + node_modules/mongoose/support/expresso/.npmignore | 3 + node_modules/mongoose/support/expresso/History.md | 128 + node_modules/mongoose/support/expresso/Makefile | 53 + node_modules/mongoose/support/expresso/Readme.md | 61 + .../mongoose/support/expresso/bin/expresso | 874 + .../mongoose/support/expresso/docs/api.html | 1080 + .../mongoose/support/expresso/docs/index.html | 377 + .../mongoose/support/expresso/docs/index.md | 290 + .../support/expresso/docs/layout/foot.html | 3 + .../support/expresso/docs/layout/head.html | 42 + node_modules/mongoose/support/expresso/lib/bar.js | 4 + node_modules/mongoose/support/expresso/lib/foo.js | 16 + .../mongoose/support/expresso/package.json | 12 + .../mongoose/support/expresso/test/assert.test.js | 91 + .../mongoose/support/expresso/test/async.test.js | 12 + .../mongoose/support/expresso/test/bar.test.js | 13 + .../mongoose/support/expresso/test/foo.test.js | 14 + .../mongoose/support/expresso/test/http.test.js | 109 + .../support/expresso/test/serial/async.test.js | 39 + .../support/expresso/test/serial/http.test.js | 48 + node_modules/mongoose/test/collection.test.js | 116 + node_modules/mongoose/test/common.js | 145 + node_modules/mongoose/test/connection.test.js | 310 + node_modules/mongoose/test/crash.test.js | 36 + node_modules/mongoose/test/document.strict.test.js | 178 + node_modules/mongoose/test/document.test.js | 912 + .../drivers/node-mongodb-native/collection.test.js | 63 + node_modules/mongoose/test/dropdb.js | 7 + node_modules/mongoose/test/index.test.js | 235 + node_modules/mongoose/test/model.querying.test.js | 2352 + node_modules/mongoose/test/model.ref.test.js | 1528 + node_modules/mongoose/test/model.stream.test.js | 232 + node_modules/mongoose/test/model.test.js | 4682 + node_modules/mongoose/test/model.update.test.js | 526 + node_modules/mongoose/test/namedscope.test.js | 253 + node_modules/mongoose/test/promise.test.js | 167 + node_modules/mongoose/test/query.test.js | 890 + node_modules/mongoose/test/schema.onthefly.test.js | 105 + node_modules/mongoose/test/schema.test.js | 978 + node_modules/mongoose/test/shard.test.js | 216 + node_modules/mongoose/test/types.array.test.js | 551 + node_modules/mongoose/test/types.buffer.test.js | 350 + node_modules/mongoose/test/types.document.test.js | 197 + .../mongoose/test/types.documentarray.test.js | 132 + node_modules/mongoose/test/types.number.test.js | 37 + node_modules/mongoose/test/utils.test.js | 196 + node_modules/mongoose/test/zzlast.test.js | 11 + node_modules/request/LICENSE | 55 + node_modules/request/README.md | 285 + node_modules/request/main.js | 631 + node_modules/request/mimetypes.js | 146 + node_modules/request/oauth.js | 34 + node_modules/request/package.json | 41 + node_modules/request/tests/googledoodle.png | Bin 0 -> 38510 bytes node_modules/request/tests/run.sh | 6 + node_modules/request/tests/server.js | 75 + node_modules/request/tests/ssl/test.crt | 15 + node_modules/request/tests/ssl/test.key | 15 + node_modules/request/tests/test-body.js | 95 + node_modules/request/tests/test-cookie.js | 29 + node_modules/request/tests/test-cookiejar.js | 90 + node_modules/request/tests/test-errors.js | 30 + node_modules/request/tests/test-httpModule.js | 94 + node_modules/request/tests/test-https.js | 86 + node_modules/request/tests/test-oauth.js | 117 + node_modules/request/tests/test-pipes.js | 182 + node_modules/request/tests/test-proxy.js | 39 + node_modules/request/tests/test-redirect.js | 76 + node_modules/request/tests/test-timeout.js | 87 + node_modules/request/uuid.js | 19 + node_modules/request/vendor/cookie/index.js | 60 + node_modules/request/vendor/cookie/jar.js | 72 + node_modules/should/.gitmodules | 3 + node_modules/should/.npmignore | 1 + node_modules/should/History.md | 100 + node_modules/should/Makefile | 6 + node_modules/should/Readme.md | 367 + node_modules/should/examples/runner.js | 53 + node_modules/should/index.js | 2 + node_modules/should/lib/eql.js | 91 + node_modules/should/lib/should.js | 701 + node_modules/should/package.json | 39 + node_modules/should/test/exist.test.js | 96 + node_modules/should/test/should.test.js | 544 + node_modules/socket.io-client/.npmignore | 2 + node_modules/socket.io-client/History.md | 182 + node_modules/socket.io-client/Makefile | 20 + node_modules/socket.io-client/README.md | 246 + node_modules/socket.io-client/bin/builder.js | 281 + .../socket.io-client/dist/WebSocketMain.swf | Bin 0 -> 175830 bytes .../dist/WebSocketMainInsecure.swf | Bin 0 -> 175953 bytes node_modules/socket.io-client/dist/socket.io.js | 3787 + .../socket.io-client/dist/socket.io.min.js | 2 + node_modules/socket.io-client/lib/events.js | 184 + node_modules/socket.io-client/lib/io.js | 206 + node_modules/socket.io-client/lib/json.js | 322 + node_modules/socket.io-client/lib/namespace.js | 242 + node_modules/socket.io-client/lib/parser.js | 262 + node_modules/socket.io-client/lib/socket.js | 551 + node_modules/socket.io-client/lib/transport.js | 245 + .../socket.io-client/lib/transports/flashsocket.js | 191 + .../socket.io-client/lib/transports/htmlfile.js | 171 + .../lib/transports/jsonp-polling.js | 255 + .../socket.io-client/lib/transports/websocket.js | 184 + .../socket.io-client/lib/transports/xhr-polling.js | 161 + .../socket.io-client/lib/transports/xhr.js | 217 + node_modules/socket.io-client/lib/util.js | 356 + .../lib/vendor/web-socket-js/.npmignore | 1 + .../lib/vendor/web-socket-js/README.md | 157 + .../lib/vendor/web-socket-js/WebSocketMain.swf | Bin 0 -> 175830 bytes .../web-socket-js/flash-src/IWebSocketLogger.as | 8 + .../vendor/web-socket-js/flash-src/WebSocket.as | 464 + .../web-socket-js/flash-src/WebSocketEvent.as | 33 + .../web-socket-js/flash-src/WebSocketMain.as | 150 + .../flash-src/WebSocketMainInsecure.as | 19 + .../lib/vendor/web-socket-js/flash-src/build.sh | 10 + .../com/adobe/net/proxies/RFC2817Socket.as | 204 + .../flash-src/com/gsolo/encryption/MD5.as | 375 + .../flash-src/com/hurlant/crypto/Crypto.as | 287 + .../hurlant/crypto/cert/MozillaRootCertificates.as | 3235 + .../com/hurlant/crypto/cert/X509Certificate.as | 218 + .../crypto/cert/X509CertificateCollection.as | 57 + .../flash-src/com/hurlant/crypto/hash/HMAC.as | 82 + .../flash-src/com/hurlant/crypto/hash/IHMAC.as | 27 + .../flash-src/com/hurlant/crypto/hash/IHash.as | 21 + .../flash-src/com/hurlant/crypto/hash/MAC.as | 137 + .../flash-src/com/hurlant/crypto/hash/MD2.as | 124 + .../flash-src/com/hurlant/crypto/hash/MD5.as | 204 + .../flash-src/com/hurlant/crypto/hash/SHA1.as | 106 + .../flash-src/com/hurlant/crypto/hash/SHA224.as | 28 + .../flash-src/com/hurlant/crypto/hash/SHA256.as | 115 + .../flash-src/com/hurlant/crypto/hash/SHABase.as | 71 + .../flash-src/com/hurlant/crypto/prng/ARC4.as | 90 + .../flash-src/com/hurlant/crypto/prng/IPRNG.as | 20 + .../flash-src/com/hurlant/crypto/prng/Random.as | 119 + .../flash-src/com/hurlant/crypto/prng/TLSPRF.as | 142 + .../flash-src/com/hurlant/crypto/rsa/RSAKey.as | 339 + .../com/hurlant/crypto/symmetric/AESKey.as | 2797 + .../com/hurlant/crypto/symmetric/BlowFishKey.as | 375 + .../com/hurlant/crypto/symmetric/CBCMode.as | 55 + .../com/hurlant/crypto/symmetric/CFB8Mode.as | 61 + .../com/hurlant/crypto/symmetric/CFBMode.as | 64 + .../com/hurlant/crypto/symmetric/CTRMode.as | 58 + .../com/hurlant/crypto/symmetric/DESKey.as | 365 + .../com/hurlant/crypto/symmetric/ECBMode.as | 86 + .../com/hurlant/crypto/symmetric/ICipher.as | 21 + .../com/hurlant/crypto/symmetric/IMode.as | 15 + .../flash-src/com/hurlant/crypto/symmetric/IPad.as | 32 + .../com/hurlant/crypto/symmetric/IStreamCipher.as | 21 + .../com/hurlant/crypto/symmetric/ISymmetricKey.as | 35 + .../com/hurlant/crypto/symmetric/IVMode.as | 110 + .../com/hurlant/crypto/symmetric/NullPad.as | 34 + .../com/hurlant/crypto/symmetric/OFBMode.as | 52 + .../com/hurlant/crypto/symmetric/PKCS5.as | 44 + .../com/hurlant/crypto/symmetric/SSLPad.as | 44 + .../com/hurlant/crypto/symmetric/SimpleIVMode.as | 60 + .../com/hurlant/crypto/symmetric/TLSPad.as | 42 + .../com/hurlant/crypto/symmetric/TripleDESKey.as | 88 + .../com/hurlant/crypto/symmetric/XTeaKey.as | 94 + .../com/hurlant/crypto/symmetric/aeskey.pl | 29 + .../com/hurlant/crypto/symmetric/dump.txt | 2304 + .../com/hurlant/crypto/tests/AESKeyTest.as | 1220 + .../flash-src/com/hurlant/crypto/tests/ARC4Test.as | 58 + .../com/hurlant/crypto/tests/BigIntegerTest.as | 39 + .../com/hurlant/crypto/tests/BlowFishKeyTest.as | 148 + .../com/hurlant/crypto/tests/CBCModeTest.as | 160 + .../com/hurlant/crypto/tests/CFB8ModeTest.as | 71 + .../com/hurlant/crypto/tests/CFBModeTest.as | 98 + .../com/hurlant/crypto/tests/CTRModeTest.as | 109 + .../com/hurlant/crypto/tests/DESKeyTest.as | 112 + .../com/hurlant/crypto/tests/ECBModeTest.as | 151 + .../flash-src/com/hurlant/crypto/tests/HMACTest.as | 184 + .../com/hurlant/crypto/tests/ITestHarness.as | 20 + .../flash-src/com/hurlant/crypto/tests/MD2Test.as | 56 + .../flash-src/com/hurlant/crypto/tests/MD5Test.as | 58 + .../com/hurlant/crypto/tests/OFBModeTest.as | 101 + .../com/hurlant/crypto/tests/RSAKeyTest.as | 92 + .../flash-src/com/hurlant/crypto/tests/SHA1Test.as | 198 + .../com/hurlant/crypto/tests/SHA224Test.as | 58 + .../com/hurlant/crypto/tests/SHA256Test.as | 60 + .../com/hurlant/crypto/tests/TLSPRFTest.as | 51 + .../flash-src/com/hurlant/crypto/tests/TestCase.as | 42 + .../com/hurlant/crypto/tests/TripleDESKeyTest.as | 59 + .../com/hurlant/crypto/tests/XTeaKeyTest.as | 66 + .../com/hurlant/crypto/tls/BulkCiphers.as | 102 + .../com/hurlant/crypto/tls/CipherSuites.as | 117 + .../com/hurlant/crypto/tls/IConnectionState.as | 14 + .../com/hurlant/crypto/tls/ISecurityParameters.as | 29 + .../com/hurlant/crypto/tls/KeyExchanges.as | 24 + .../flash-src/com/hurlant/crypto/tls/MACs.as | 38 + .../com/hurlant/crypto/tls/SSLConnectionState.as | 171 + .../flash-src/com/hurlant/crypto/tls/SSLEvent.as | 26 + .../hurlant/crypto/tls/SSLSecurityParameters.as | 340 + .../flash-src/com/hurlant/crypto/tls/TLSConfig.as | 70 + .../com/hurlant/crypto/tls/TLSConnectionState.as | 151 + .../flash-src/com/hurlant/crypto/tls/TLSEngine.as | 895 + .../flash-src/com/hurlant/crypto/tls/TLSError.as | 39 + .../flash-src/com/hurlant/crypto/tls/TLSEvent.as | 27 + .../hurlant/crypto/tls/TLSSecurityParameters.as | 197 + .../flash-src/com/hurlant/crypto/tls/TLSSocket.as | 370 + .../com/hurlant/crypto/tls/TLSSocketEvent.as | 26 + .../flash-src/com/hurlant/crypto/tls/TLSTest.as | 180 + .../flash-src/com/hurlant/math/BarrettReduction.as | 90 + .../flash-src/com/hurlant/math/BigInteger.as | 1543 + .../flash-src/com/hurlant/math/ClassicReduction.as | 35 + .../flash-src/com/hurlant/math/IReduction.as | 11 + .../com/hurlant/math/MontgomeryReduction.as | 85 + .../flash-src/com/hurlant/math/NullReduction.as | 34 + .../flash-src/com/hurlant/math/bi_internal.as | 11 + .../flash-src/com/hurlant/util/ArrayUtil.as | 25 + .../flash-src/com/hurlant/util/Base64.as | 189 + .../flash-src/com/hurlant/util/Hex.as | 66 + .../flash-src/com/hurlant/util/Memory.as | 28 + .../flash-src/com/hurlant/util/der/ByteString.as | 43 + .../flash-src/com/hurlant/util/der/DER.as | 210 + .../flash-src/com/hurlant/util/der/IAsn1Type.as | 21 + .../flash-src/com/hurlant/util/der/Integer.as | 44 + .../flash-src/com/hurlant/util/der/OID.as | 35 + .../com/hurlant/util/der/ObjectIdentifier.as | 112 + .../flash-src/com/hurlant/util/der/PEM.as | 118 + .../com/hurlant/util/der/PrintableString.as | 49 + .../flash-src/com/hurlant/util/der/Sequence.as | 90 + .../flash-src/com/hurlant/util/der/Set.as | 27 + .../flash-src/com/hurlant/util/der/Type.as | 94 + .../flash-src/com/hurlant/util/der/UTCTime.as | 60 + .../lib/vendor/web-socket-js/sample.html | 75 + .../lib/vendor/web-socket-js/swfobject.js | 6 + .../lib/vendor/web-socket-js/web_socket.js | 349 + .../socket.io-client/node_modules/.bin/uglifyjs | 323 + .../socket.io-client/node_modules/.bin/wscat | 185 + .../node_modules/active-x-obfuscator/.npmignore | 2 + .../node_modules/active-x-obfuscator/Readme.md | 33 + .../node_modules/active-x-obfuscator/index.js | 83 + .../node_modules/zeparser/.npmignore | 1 + .../node_modules/zeparser/LICENSE | 19 + .../node_modules/zeparser/README | 37 + .../node_modules/zeparser/Tokenizer.js | 646 + .../node_modules/zeparser/ZeParser.js | 2180 + .../node_modules/zeparser/benchmark.html | 111608 ++++++++++++++++++ .../node_modules/zeparser/index.js | 1 + .../node_modules/zeparser/package.json | 30 + .../node_modules/zeparser/test-parser.html | 26 + .../node_modules/zeparser/test-tokenizer.html | 23 + .../node_modules/zeparser/tests.js | 478 + .../node_modules/zeparser/unicodecategories.js | 49 + .../node_modules/active-x-obfuscator/package.json | 36 + .../node_modules/active-x-obfuscator/test.js | 53 + .../node_modules/uglify-js/.npmignore | 4 + .../node_modules/uglify-js/README.html | 981 + .../node_modules/uglify-js/README.org | 574 + .../node_modules/uglify-js/bin/uglifyjs | 323 + .../node_modules/uglify-js/docstyle.css | 75 + .../node_modules/uglify-js/lib/object-ast.js | 75 + .../node_modules/uglify-js/lib/parse-js.js | 1342 + .../node_modules/uglify-js/lib/process.js | 2011 + .../node_modules/uglify-js/lib/squeeze-more.js | 69 + .../node_modules/uglify-js/package.json | 33 + .../node_modules/uglify-js/package.json~ | 24 + .../node_modules/uglify-js/test/beautify.js | 28 + .../node_modules/uglify-js/test/testparser.js | 403 + .../test/unit/compress/expected/array1.js | 1 + .../test/unit/compress/expected/array2.js | 1 + .../test/unit/compress/expected/array3.js | 1 + .../test/unit/compress/expected/array4.js | 1 + .../test/unit/compress/expected/assignment.js | 1 + .../test/unit/compress/expected/concatstring.js | 1 + .../uglify-js/test/unit/compress/expected/const.js | 1 + .../test/unit/compress/expected/empty-blocks.js | 1 + .../test/unit/compress/expected/forstatement.js | 1 + .../uglify-js/test/unit/compress/expected/if.js | 1 + .../test/unit/compress/expected/ifreturn.js | 1 + .../test/unit/compress/expected/ifreturn2.js | 1 + .../test/unit/compress/expected/issue10.js | 1 + .../test/unit/compress/expected/issue11.js | 1 + .../test/unit/compress/expected/issue13.js | 1 + .../test/unit/compress/expected/issue14.js | 1 + .../test/unit/compress/expected/issue16.js | 1 + .../test/unit/compress/expected/issue17.js | 1 + .../test/unit/compress/expected/issue20.js | 1 + .../test/unit/compress/expected/issue21.js | 1 + .../test/unit/compress/expected/issue25.js | 1 + .../test/unit/compress/expected/issue27.js | 1 + .../test/unit/compress/expected/issue278.js | 1 + .../test/unit/compress/expected/issue28.js | 1 + .../test/unit/compress/expected/issue29.js | 1 + .../test/unit/compress/expected/issue30.js | 1 + .../test/unit/compress/expected/issue34.js | 1 + .../test/unit/compress/expected/issue4.js | 1 + .../test/unit/compress/expected/issue48.js | 1 + .../test/unit/compress/expected/issue50.js | 1 + .../test/unit/compress/expected/issue53.js | 1 + .../test/unit/compress/expected/issue54.1.js | 1 + .../test/unit/compress/expected/issue68.js | 1 + .../test/unit/compress/expected/issue69.js | 1 + .../test/unit/compress/expected/issue9.js | 1 + .../test/unit/compress/expected/mangle.js | 1 + .../test/unit/compress/expected/null_string.js | 1 + .../test/unit/compress/expected/strict-equals.js | 1 + .../uglify-js/test/unit/compress/expected/var.js | 1 + .../test/unit/compress/expected/whitespace.js | 1 + .../uglify-js/test/unit/compress/expected/with.js | 1 + .../uglify-js/test/unit/compress/test/array1.js | 3 + .../uglify-js/test/unit/compress/test/array2.js | 4 + .../uglify-js/test/unit/compress/test/array3.js | 4 + .../uglify-js/test/unit/compress/test/array4.js | 6 + .../test/unit/compress/test/assignment.js | 20 + .../test/unit/compress/test/concatstring.js | 3 + .../uglify-js/test/unit/compress/test/const.js | 5 + .../test/unit/compress/test/empty-blocks.js | 4 + .../test/unit/compress/test/forstatement.js | 10 + .../uglify-js/test/unit/compress/test/if.js | 6 + .../uglify-js/test/unit/compress/test/ifreturn.js | 9 + .../uglify-js/test/unit/compress/test/ifreturn2.js | 16 + .../uglify-js/test/unit/compress/test/issue10.js | 1 + .../uglify-js/test/unit/compress/test/issue11.js | 3 + .../uglify-js/test/unit/compress/test/issue13.js | 1 + .../uglify-js/test/unit/compress/test/issue14.js | 1 + .../uglify-js/test/unit/compress/test/issue16.js | 1 + .../uglify-js/test/unit/compress/test/issue17.js | 4 + .../uglify-js/test/unit/compress/test/issue20.js | 1 + .../uglify-js/test/unit/compress/test/issue21.js | 6 + .../uglify-js/test/unit/compress/test/issue25.js | 7 + .../uglify-js/test/unit/compress/test/issue27.js | 1 + .../uglify-js/test/unit/compress/test/issue278.js | 1 + .../uglify-js/test/unit/compress/test/issue28.js | 3 + .../uglify-js/test/unit/compress/test/issue29.js | 1 + .../uglify-js/test/unit/compress/test/issue30.js | 3 + .../uglify-js/test/unit/compress/test/issue34.js | 3 + .../uglify-js/test/unit/compress/test/issue4.js | 3 + .../uglify-js/test/unit/compress/test/issue48.js | 1 + .../uglify-js/test/unit/compress/test/issue50.js | 9 + .../uglify-js/test/unit/compress/test/issue53.js | 1 + .../uglify-js/test/unit/compress/test/issue54.1.js | 3 + .../uglify-js/test/unit/compress/test/issue68.js | 5 + .../uglify-js/test/unit/compress/test/issue69.js | 1 + .../uglify-js/test/unit/compress/test/issue9.js | 4 + .../uglify-js/test/unit/compress/test/mangle.js | 5 + .../test/unit/compress/test/null_string.js | 1 + .../test/unit/compress/test/strict-equals.js | 3 + .../uglify-js/test/unit/compress/test/var.js | 3 + .../test/unit/compress/test/whitespace.js | 21 + .../uglify-js/test/unit/compress/test/with.js | 2 + .../node_modules/uglify-js/test/unit/scripts.js | 55 + .../node_modules/uglify-js/tmp/269.js | 13 + .../node_modules/uglify-js/tmp/app.js | 22315 ++++ .../node_modules/uglify-js/tmp/embed-tokens.js | 15 + .../node_modules/uglify-js/tmp/goto.js | 26 + .../node_modules/uglify-js/tmp/goto2.js | 8 + .../node_modules/uglify-js/tmp/hoist.js | 33 + .../node_modules/uglify-js/tmp/instrument.js | 97 + .../node_modules/uglify-js/tmp/instrument2.js | 138 + .../node_modules/uglify-js/tmp/liftvars.js | 8 + .../node_modules/uglify-js/tmp/test.js | 30 + .../node_modules/uglify-js/tmp/uglify-hangs.js | 3930 + .../node_modules/uglify-js/tmp/uglify-hangs2.js | 166 + .../node_modules/uglify-js/uglify-js.js | 17 + .../socket.io-client/node_modules/ws/.lock-wscript | 9 + .../socket.io-client/node_modules/ws/.npmignore | 6 + .../socket.io-client/node_modules/ws/.travis.yml | 4 + .../socket.io-client/node_modules/ws/History.md | 180 + .../socket.io-client/node_modules/ws/Makefile | 38 + .../socket.io-client/node_modules/ws/README.md | 141 + .../node_modules/ws/bench/parser.benchmark.js | 115 + .../node_modules/ws/bench/sender.benchmark.js | 66 + .../node_modules/ws/bench/speed.js | 105 + .../socket.io-client/node_modules/ws/bench/util.js | 105 + .../socket.io-client/node_modules/ws/bin/wscat | 185 + .../socket.io-client/node_modules/ws/binding.gyp | 14 + .../node_modules/ws/build/.wafpickle-7 | Bin 0 -> 1334 bytes .../node_modules/ws/build/Release/bufferutil.node | Bin 0 -> 21320 bytes .../node_modules/ws/build/Release/validation.node | Bin 0 -> 21112 bytes .../node_modules/ws/build/c4che/Release.cache.py | 49 + .../node_modules/ws/build/c4che/build.config.py | 2 + .../socket.io-client/node_modules/ws/doc/ws.md | 156 + .../node_modules/ws/examples/fileapi/.npmignore | 1 + .../node_modules/ws/examples/fileapi/package.json | 18 + .../node_modules/ws/examples/fileapi/public/app.js | 39 + .../ws/examples/fileapi/public/index.html | 22 + .../ws/examples/fileapi/public/uploader.js | 55 + .../node_modules/ws/examples/fileapi/server.js | 102 + .../ws/examples/serverstats/package.json | 17 + .../ws/examples/serverstats/public/index.html | 32 + .../node_modules/ws/examples/serverstats/server.js | 19 + .../socket.io-client/node_modules/ws/index.js | 10 + .../node_modules/ws/lib/BufferPool.js | 59 + .../node_modules/ws/lib/BufferUtil.js | 19 + .../node_modules/ws/lib/BufferUtilWindows.js | 51 + .../node_modules/ws/lib/ErrorCodes.js | 23 + .../node_modules/ws/lib/Receiver.hixie.js | 142 + .../node_modules/ws/lib/Receiver.js | 586 + .../node_modules/ws/lib/Sender.hixie.js | 94 + .../socket.io-client/node_modules/ws/lib/Sender.js | 213 + .../node_modules/ws/lib/Validation.js | 19 + .../node_modules/ws/lib/ValidationWindows.js | 16 + .../node_modules/ws/lib/WebSocket.js | 637 + .../node_modules/ws/lib/WebSocketServer.js | 385 + .../socket.io-client/node_modules/ws/make.bat | 20 + .../ws/node_modules/commander/.npmignore | 4 + .../ws/node_modules/commander/.travis.yml | 4 + .../ws/node_modules/commander/History.md | 99 + .../ws/node_modules/commander/Makefile | 7 + .../ws/node_modules/commander/Readme.md | 263 + .../ws/node_modules/commander/index.js | 2 + .../ws/node_modules/commander/lib/commander.js | 992 + .../ws/node_modules/commander/package.json | 41 + .../ws/node_modules/options/.npmignore | 5 + .../node_modules/ws/node_modules/options/Makefile | 12 + .../node_modules/ws/node_modules/options/README.md | 3 + .../ws/node_modules/options/lib/options.js | 75 + .../ws/node_modules/options/package.json | 36 + .../node_modules/options/test/fixtures/test.conf | 4 + .../ws/node_modules/options/test/options.test.js | 119 + .../socket.io-client/node_modules/ws/package.json | 46 + .../node_modules/ws/src/bufferutil.cc | 113 + .../node_modules/ws/src/validation.cc | 141 + .../node_modules/ws/test/BufferPool.test.js | 63 + .../node_modules/ws/test/Receiver.hixie.test.js | 132 + .../node_modules/ws/test/Receiver.test.js | 255 + .../node_modules/ws/test/Sender.hixie.test.js | 65 + .../node_modules/ws/test/Sender.test.js | 24 + .../node_modules/ws/test/Validation.test.js | 23 + .../node_modules/ws/test/WebSocket.integration.js | 42 + .../node_modules/ws/test/WebSocket.test.js | 1426 + .../node_modules/ws/test/WebSocketServer.test.js | 982 + .../node_modules/ws/test/autobahn-server.js | 29 + .../node_modules/ws/test/autobahn.js | 52 + .../node_modules/ws/test/fixtures/certificate.pem | 13 + .../node_modules/ws/test/fixtures/key.pem | 15 + .../node_modules/ws/test/fixtures/request.pem | 11 + .../node_modules/ws/test/fixtures/textfile | 9 + .../node_modules/ws/test/hybi-common.js | 99 + .../node_modules/ws/test/testserver.js | 168 + .../socket.io-client/node_modules/ws/wscript | 38 + .../node_modules/xmlhttprequest/README.md | 19 + .../node_modules/xmlhttprequest/XMLHttpRequest.js | 309 + .../node_modules/xmlhttprequest/autotest.watchr | 8 + .../node_modules/xmlhttprequest/demo.js | 16 + .../node_modules/xmlhttprequest/package.json | 37 + .../xmlhttprequest/tests/test-constants.js | 13 + .../xmlhttprequest/tests/test-headers.js | 35 + .../xmlhttprequest/tests/test-request.js | 62 + node_modules/socket.io-client/package.json | 69 + node_modules/socket.io-client/test/events.test.js | 121 + node_modules/socket.io-client/test/io.test.js | 31 + .../socket.io-client/test/node/builder.common.js | 102 + .../socket.io-client/test/node/builder.test.js | 131 + node_modules/socket.io-client/test/parser.test.js | 360 + node_modules/socket.io-client/test/socket.test.js | 379 + node_modules/socket.io-client/test/util.test.js | 156 + node_modules/socket.io-client/test/worker.js | 20 + node_modules/socket.io/.npmignore | 3 + node_modules/socket.io/.travis.yml | 6 + node_modules/socket.io/History.md | 246 + node_modules/socket.io/Makefile | 31 + node_modules/socket.io/Readme.md | 345 + node_modules/socket.io/benchmarks/decode.bench.js | 64 + node_modules/socket.io/benchmarks/encode.bench.js | 90 + node_modules/socket.io/benchmarks/runner.js | 55 + node_modules/socket.io/examples/chat/app.js | 80 + node_modules/socket.io/examples/chat/index.jade | 83 + node_modules/socket.io/examples/chat/package.json | 11 + .../examples/chat/public/stylesheets/mixins.styl | 96 + .../examples/chat/public/stylesheets/style.css | 188 + .../examples/chat/public/stylesheets/style.styl | 118 + node_modules/socket.io/examples/irc-output/app.js | 74 + .../socket.io/examples/irc-output/index.jade | 28 + node_modules/socket.io/examples/irc-output/irc.js | 164 + .../socket.io/examples/irc-output/package.json | 10 + .../irc-output/public/stylesheets/style.styl | 69 + node_modules/socket.io/index.js | 8 + node_modules/socket.io/lib/client.js | 167 + node_modules/socket.io/lib/logger.js | 97 + node_modules/socket.io/lib/manager.js | 983 + node_modules/socket.io/lib/namespace.js | 355 + node_modules/socket.io/lib/parser.js | 249 + node_modules/socket.io/lib/socket.io.js | 136 + node_modules/socket.io/lib/socket.js | 369 + node_modules/socket.io/lib/static.js | 395 + node_modules/socket.io/lib/store.js | 98 + node_modules/socket.io/lib/stores/memory.js | 143 + node_modules/socket.io/lib/stores/redis.js | 269 + node_modules/socket.io/lib/transport.js | 534 + .../socket.io/lib/transports/flashsocket.js | 106 + node_modules/socket.io/lib/transports/htmlfile.js | 82 + .../socket.io/lib/transports/http-polling.js | 135 + node_modules/socket.io/lib/transports/http.js | 118 + node_modules/socket.io/lib/transports/index.js | 12 + .../socket.io/lib/transports/jsonp-polling.js | 96 + node_modules/socket.io/lib/transports/websocket.js | 36 + .../socket.io/lib/transports/websocket/default.js | 360 + .../lib/transports/websocket/hybi-07-12.js | 622 + .../socket.io/lib/transports/websocket/hybi-16.js | 623 + .../socket.io/lib/transports/websocket/index.js | 11 + .../socket.io/lib/transports/xhr-polling.js | 69 + node_modules/socket.io/lib/util.js | 50 + node_modules/socket.io/package.json | 69 + .../support/node-websocket-client/LICENSE | 27 + .../support/node-websocket-client/Makefile | 22 + .../support/node-websocket-client/README.md | 41 + .../node-websocket-client/examples/client-unix.js | 12 + .../node-websocket-client/examples/client.js | 10 + .../node-websocket-client/examples/server-unix.js | 13 + .../support/node-websocket-client/lib/websocket.js | 617 + .../support/node-websocket-client/package.json | 22 + .../node-websocket-client/test/test-basic.js | 68 + .../test/test-client-close.js | 43 + .../test/test-readonly-attrs.js | 43 + .../node-websocket-client/test/test-ready-state.js | 26 + .../test/test-server-close.js | 41 + .../test/test-unix-send-fd.js | 63 + .../test/test-unix-sockets.js | 46 + 985 files changed, 310050 insertions(+) create mode 100644 node_modules/ejs/.gitmodules create mode 100644 node_modules/ejs/.npmignore create mode 100644 node_modules/ejs/History.md create mode 100644 node_modules/ejs/Makefile create mode 100644 node_modules/ejs/Readme.md create mode 100644 node_modules/ejs/benchmark.js create mode 100644 node_modules/ejs/ejs.js create mode 100644 node_modules/ejs/ejs.min.js create mode 100644 node_modules/ejs/examples/client.html create mode 100644 node_modules/ejs/examples/list.ejs create mode 100644 node_modules/ejs/examples/list.js create mode 100644 node_modules/ejs/index.js create mode 100644 node_modules/ejs/lib/ejs.js create mode 100644 node_modules/ejs/lib/filters.js create mode 100644 node_modules/ejs/lib/utils.js create mode 100644 node_modules/ejs/package.json create mode 100644 node_modules/ejs/support/compile.js create mode 100644 node_modules/ejs/test/ejs.test.js create mode 100644 node_modules/ejs/test/fixtures/user.ejs create mode 100644 node_modules/express/.npmignore create mode 100644 node_modules/express/History.md create mode 100644 node_modules/express/LICENSE create mode 100644 node_modules/express/Makefile create mode 100644 node_modules/express/Readme.md create mode 100755 node_modules/express/bin/express create mode 100644 node_modules/express/index.js create mode 100644 node_modules/express/lib/express.js create mode 100644 node_modules/express/lib/http.js create mode 100644 node_modules/express/lib/https.js create mode 100644 node_modules/express/lib/request.js create mode 100644 node_modules/express/lib/response.js create mode 100644 node_modules/express/lib/router/collection.js create mode 100644 node_modules/express/lib/router/index.js create mode 100644 node_modules/express/lib/router/methods.js create mode 100644 node_modules/express/lib/router/route.js create mode 100644 node_modules/express/lib/utils.js create mode 100644 node_modules/express/lib/view.js create mode 100644 node_modules/express/lib/view/partial.js create mode 100644 node_modules/express/lib/view/view.js create mode 100644 node_modules/express/node_modules/connect/.npmignore create mode 100644 node_modules/express/node_modules/connect/LICENSE create mode 100644 node_modules/express/node_modules/connect/index.js create mode 100644 node_modules/express/node_modules/connect/lib/cache.js create mode 100644 node_modules/express/node_modules/connect/lib/connect.js create mode 100644 node_modules/express/node_modules/connect/lib/http.js create mode 100644 node_modules/express/node_modules/connect/lib/https.js create mode 100644 node_modules/express/node_modules/connect/lib/index.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/basicAuth.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/bodyParser.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/compiler.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/cookieParser.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/csrf.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/directory.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/errorHandler.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/favicon.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/limit.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/logger.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/methodOverride.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/profiler.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/query.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/responseTime.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/router.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/session.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/session/cookie.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/session/memory.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/session/session.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/session/store.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/static.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/staticCache.js create mode 100644 node_modules/express/node_modules/connect/lib/middleware/vhost.js create mode 100644 node_modules/express/node_modules/connect/lib/patch.js create mode 100644 node_modules/express/node_modules/connect/lib/public/directory.html create mode 100644 node_modules/express/node_modules/connect/lib/public/error.html create mode 100644 node_modules/express/node_modules/connect/lib/public/favicon.ico create mode 100644 node_modules/express/node_modules/connect/lib/public/style.css create mode 100644 node_modules/express/node_modules/connect/lib/utils.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/.npmignore create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/.travis.yml create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/Makefile create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/Readme.md create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/TODO create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/benchmark/bench-multipart-parser.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/example/post.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/example/upload.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/index.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/lib/file.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/lib/incoming_form.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/lib/index.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/lib/multipart_parser.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/lib/querystring_parser.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/lib/util.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/package.json create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/common.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/file/funkyfilename.txt create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/file/plain.txt create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/info.md create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/js/no-filename.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/js/special-chars-in-filename.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/multipart.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/integration/test-fixtures.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/common.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/integration/test-multipart-parser.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-file.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-incoming-form.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-multipart-parser.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-querystring-parser.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/system/test-multi-video-upload.js create mode 100755 node_modules/express/node_modules/connect/node_modules/formidable/test/run.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/test/unit/test-incoming-form.js create mode 100644 node_modules/express/node_modules/connect/node_modules/formidable/tool/record.js create mode 100644 node_modules/express/node_modules/connect/package.json create mode 100644 node_modules/express/node_modules/mime/LICENSE create mode 100644 node_modules/express/node_modules/mime/README.md create mode 100644 node_modules/express/node_modules/mime/mime.js create mode 100644 node_modules/express/node_modules/mime/package.json create mode 100644 node_modules/express/node_modules/mime/test.js create mode 100644 node_modules/express/node_modules/mime/types/mime.types create mode 100644 node_modules/express/node_modules/mime/types/node.types create mode 100644 node_modules/express/node_modules/mkdirp/.gitignore.orig create mode 100644 node_modules/express/node_modules/mkdirp/.gitignore.rej create mode 100644 node_modules/express/node_modules/mkdirp/.npmignore create mode 100644 node_modules/express/node_modules/mkdirp/LICENSE create mode 100644 node_modules/express/node_modules/mkdirp/README.markdown create mode 100644 node_modules/express/node_modules/mkdirp/examples/pow.js create mode 100644 node_modules/express/node_modules/mkdirp/examples/pow.js.orig create mode 100644 node_modules/express/node_modules/mkdirp/examples/pow.js.rej create mode 100644 node_modules/express/node_modules/mkdirp/index.js create mode 100644 node_modules/express/node_modules/mkdirp/package.json create mode 100644 node_modules/express/node_modules/mkdirp/test/chmod.js create mode 100644 node_modules/express/node_modules/mkdirp/test/clobber.js create mode 100644 node_modules/express/node_modules/mkdirp/test/mkdirp.js create mode 100644 node_modules/express/node_modules/mkdirp/test/perm.js create mode 100644 node_modules/express/node_modules/mkdirp/test/perm_sync.js create mode 100644 node_modules/express/node_modules/mkdirp/test/race.js create mode 100644 node_modules/express/node_modules/mkdirp/test/rel.js create mode 100644 node_modules/express/node_modules/mkdirp/test/sync.js create mode 100644 node_modules/express/node_modules/mkdirp/test/umask.js create mode 100644 node_modules/express/node_modules/mkdirp/test/umask_sync.js create mode 100644 node_modules/express/node_modules/qs/.gitmodules create mode 100644 node_modules/express/node_modules/qs/.npmignore create mode 100644 node_modules/express/node_modules/qs/.travis.yml create mode 100644 node_modules/express/node_modules/qs/History.md create mode 100644 node_modules/express/node_modules/qs/Makefile create mode 100644 node_modules/express/node_modules/qs/Readme.md create mode 100644 node_modules/express/node_modules/qs/benchmark.js create mode 100644 node_modules/express/node_modules/qs/examples.js create mode 100644 node_modules/express/node_modules/qs/index.js create mode 100644 node_modules/express/node_modules/qs/lib/querystring.js create mode 100644 node_modules/express/node_modules/qs/package.json create mode 100644 node_modules/express/node_modules/qs/test/mocha.opts create mode 100644 node_modules/express/node_modules/qs/test/parse.js create mode 100644 node_modules/express/node_modules/qs/test/stringify.js create mode 100644 node_modules/express/package.json create mode 100644 node_modules/express/testing/foo/app.js create mode 100644 node_modules/express/testing/foo/package.json create mode 100644 node_modules/express/testing/foo/public/stylesheets/style.css create mode 100644 node_modules/express/testing/foo/routes/index.js create mode 100644 node_modules/express/testing/foo/views/index.jade create mode 100644 node_modules/express/testing/foo/views/layout.jade create mode 100644 node_modules/express/testing/index.js create mode 100644 node_modules/express/testing/public/test.txt create mode 100644 node_modules/express/testing/views/page.html create mode 100644 node_modules/express/testing/views/page.jade create mode 100644 node_modules/express/testing/views/test.md create mode 100644 node_modules/express/testing/views/user/index.jade create mode 100644 node_modules/express/testing/views/user/list.jade create mode 100644 node_modules/hooks/.npmignore create mode 100644 node_modules/hooks/Makefile create mode 100644 node_modules/hooks/README.md create mode 100644 node_modules/hooks/hooks.alt.js create mode 100644 node_modules/hooks/hooks.js create mode 100644 node_modules/hooks/package.json create mode 100644 node_modules/hooks/test.js create mode 100644 node_modules/mocha/.npmignore create mode 100644 node_modules/mocha/.travis.yml create mode 100644 node_modules/mocha/History.md create mode 100644 node_modules/mocha/LICENSE create mode 100644 node_modules/mocha/Makefile create mode 100644 node_modules/mocha/Readme.md create mode 100644 node_modules/mocha/_mocha.js create mode 100755 node_modules/mocha/bin/_mocha create mode 100755 node_modules/mocha/bin/mocha create mode 100644 node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - after each.tmSnippet create mode 100644 node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - after.tmSnippet create mode 100644 node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - before each.tmSnippet create mode 100644 node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - before.tmSnippet create mode 100644 node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - it.tmSnippet create mode 100644 node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/untitled.tmSnippet create mode 100644 node_modules/mocha/editors/JavaScript mocha.tmbundle/info.plist create mode 100644 node_modules/mocha/images/error.png create mode 100644 node_modules/mocha/images/ok.png create mode 100644 node_modules/mocha/index.js create mode 100644 node_modules/mocha/lib/browser/debug.js create mode 100644 node_modules/mocha/lib/browser/diff.js create mode 100644 node_modules/mocha/lib/browser/events.js create mode 100644 node_modules/mocha/lib/browser/fs.js create mode 100644 node_modules/mocha/lib/browser/path.js create mode 100644 node_modules/mocha/lib/browser/progress.js create mode 100644 node_modules/mocha/lib/browser/tty.js create mode 100644 node_modules/mocha/lib/context.js create mode 100644 node_modules/mocha/lib/hook.js create mode 100644 node_modules/mocha/lib/interfaces/bdd.js create mode 100644 node_modules/mocha/lib/interfaces/exports.js create mode 100644 node_modules/mocha/lib/interfaces/index.js create mode 100644 node_modules/mocha/lib/interfaces/qunit.js create mode 100644 node_modules/mocha/lib/interfaces/tdd.js create mode 100644 node_modules/mocha/lib/mocha.js create mode 100644 node_modules/mocha/lib/reporters/base.js create mode 100644 node_modules/mocha/lib/reporters/doc.js create mode 100644 node_modules/mocha/lib/reporters/dot.js create mode 100644 node_modules/mocha/lib/reporters/html-cov.js create mode 100644 node_modules/mocha/lib/reporters/html.js create mode 100644 node_modules/mocha/lib/reporters/index.js create mode 100644 node_modules/mocha/lib/reporters/json-cov.js create mode 100644 node_modules/mocha/lib/reporters/json-stream.js create mode 100644 node_modules/mocha/lib/reporters/json.js create mode 100644 node_modules/mocha/lib/reporters/landing.js create mode 100644 node_modules/mocha/lib/reporters/list.js create mode 100644 node_modules/mocha/lib/reporters/markdown.js create mode 100644 node_modules/mocha/lib/reporters/min.js create mode 100644 node_modules/mocha/lib/reporters/progress.js create mode 100644 node_modules/mocha/lib/reporters/spec.js create mode 100644 node_modules/mocha/lib/reporters/tap.js create mode 100644 node_modules/mocha/lib/reporters/teamcity.js create mode 100644 node_modules/mocha/lib/reporters/templates/coverage.jade create mode 100644 node_modules/mocha/lib/reporters/templates/menu.jade create mode 100644 node_modules/mocha/lib/reporters/templates/script.html create mode 100644 node_modules/mocha/lib/reporters/templates/style.html create mode 100644 node_modules/mocha/lib/reporters/xunit.js create mode 100644 node_modules/mocha/lib/runnable.js create mode 100644 node_modules/mocha/lib/runner.js create mode 100644 node_modules/mocha/lib/suite.js create mode 100644 node_modules/mocha/lib/test.js create mode 100644 node_modules/mocha/lib/utils.js create mode 100644 node_modules/mocha/mocha.css create mode 100644 node_modules/mocha/mocha.js create mode 100755 node_modules/mocha/node_modules/.bin/jade create mode 100644 node_modules/mocha/node_modules/commander/.npmignore create mode 100644 node_modules/mocha/node_modules/commander/.travis.yml create mode 100644 node_modules/mocha/node_modules/commander/History.md create mode 100644 node_modules/mocha/node_modules/commander/Makefile create mode 100644 node_modules/mocha/node_modules/commander/Readme.md create mode 100644 node_modules/mocha/node_modules/commander/index.js create mode 100644 node_modules/mocha/node_modules/commander/lib/commander.js create mode 100644 node_modules/mocha/node_modules/commander/package.json create mode 100644 node_modules/mocha/node_modules/debug/.npmignore create mode 100644 node_modules/mocha/node_modules/debug/History.md create mode 100644 node_modules/mocha/node_modules/debug/Makefile create mode 100644 node_modules/mocha/node_modules/debug/Readme.md create mode 100644 node_modules/mocha/node_modules/debug/debug.js create mode 100644 node_modules/mocha/node_modules/debug/example/app.js create mode 100644 node_modules/mocha/node_modules/debug/example/browser.html create mode 100644 node_modules/mocha/node_modules/debug/example/wildcards.js create mode 100644 node_modules/mocha/node_modules/debug/example/worker.js create mode 100644 node_modules/mocha/node_modules/debug/index.js create mode 100644 node_modules/mocha/node_modules/debug/lib/debug.js create mode 100644 node_modules/mocha/node_modules/debug/package.json create mode 100644 node_modules/mocha/node_modules/diff/LICENSE create mode 100644 node_modules/mocha/node_modules/diff/README.md create mode 100644 node_modules/mocha/node_modules/diff/diff.js create mode 100644 node_modules/mocha/node_modules/diff/index.html create mode 100644 node_modules/mocha/node_modules/diff/package.json create mode 100644 node_modules/mocha/node_modules/diff/style.css create mode 100644 node_modules/mocha/node_modules/diff/test/diffTest.js create mode 100644 node_modules/mocha/node_modules/growl/History.md create mode 100644 node_modules/mocha/node_modules/growl/Readme.md create mode 100644 node_modules/mocha/node_modules/growl/lib/growl.js create mode 100644 node_modules/mocha/node_modules/growl/package.json create mode 100644 node_modules/mocha/node_modules/growl/test.js create mode 100644 node_modules/mocha/node_modules/jade/.npmignore create mode 100644 node_modules/mocha/node_modules/jade/LICENSE create mode 100755 node_modules/mocha/node_modules/jade/bin/jade create mode 100644 node_modules/mocha/node_modules/jade/index.js create mode 100644 node_modules/mocha/node_modules/jade/jade.js create mode 100644 node_modules/mocha/node_modules/jade/jade.min.js create mode 100644 node_modules/mocha/node_modules/jade/lib/compiler.js create mode 100644 node_modules/mocha/node_modules/jade/lib/doctypes.js create mode 100644 node_modules/mocha/node_modules/jade/lib/filters.js create mode 100644 node_modules/mocha/node_modules/jade/lib/inline-tags.js create mode 100644 node_modules/mocha/node_modules/jade/lib/jade.js create mode 100644 node_modules/mocha/node_modules/jade/lib/lexer.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/block-comment.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/block.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/case.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/code.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/comment.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/doctype.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/each.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/filter.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/index.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/literal.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/mixin.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/node.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/tag.js create mode 100644 node_modules/mocha/node_modules/jade/lib/nodes/text.js create mode 100644 node_modules/mocha/node_modules/jade/lib/parser.js create mode 100644 node_modules/mocha/node_modules/jade/lib/runtime.js create mode 100644 node_modules/mocha/node_modules/jade/lib/self-closing.js create mode 100644 node_modules/mocha/node_modules/jade/lib/utils.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/.gitignore.orig create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/.gitignore.rej create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/.npmignore create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/.travis.yml create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/LICENSE create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/README.markdown create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js.orig create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js.rej create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/index.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/package.json create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/chmod.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/clobber.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/mkdirp.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/perm.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/perm_sync.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/race.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/rel.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/return.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/return_sync.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/sync.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/umask.js create mode 100644 node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/umask_sync.js create mode 100644 node_modules/mocha/node_modules/jade/package.json create mode 100644 node_modules/mocha/node_modules/jade/runtime.js create mode 100644 node_modules/mocha/node_modules/jade/runtime.min.js create mode 100644 node_modules/mocha/package.json create mode 100644 node_modules/mongodb/.travis.yml create mode 100644 node_modules/mongodb/Makefile create mode 100644 node_modules/mongodb/external-libs/bson/Makefile create mode 100644 node_modules/mongodb/external-libs/bson/bson.cc create mode 100644 node_modules/mongodb/external-libs/bson/bson.h create mode 100644 node_modules/mongodb/external-libs/bson/index.js create mode 100644 node_modules/mongodb/external-libs/bson/test/test_bson.js create mode 100644 node_modules/mongodb/external-libs/bson/test/test_full_bson.js create mode 100644 node_modules/mongodb/external-libs/bson/test/test_stackless_bson.js create mode 100644 node_modules/mongodb/external-libs/bson/wscript create mode 100755 node_modules/mongodb/index.js create mode 100644 node_modules/mongodb/install.js create mode 100644 node_modules/mongodb/lib/mongodb/admin.js create mode 100644 node_modules/mongodb/lib/mongodb/collection.js create mode 100644 node_modules/mongodb/lib/mongodb/commands/base_command.js create mode 100644 node_modules/mongodb/lib/mongodb/commands/db_command.js create mode 100644 node_modules/mongodb/lib/mongodb/commands/delete_command.js create mode 100644 node_modules/mongodb/lib/mongodb/commands/get_more_command.js create mode 100644 node_modules/mongodb/lib/mongodb/commands/insert_command.js create mode 100644 node_modules/mongodb/lib/mongodb/commands/kill_cursor_command.js create mode 100644 node_modules/mongodb/lib/mongodb/commands/query_command.js create mode 100644 node_modules/mongodb/lib/mongodb/commands/update_command.js create mode 100644 node_modules/mongodb/lib/mongodb/connection/connection.js create mode 100644 node_modules/mongodb/lib/mongodb/connection/connection_pool.js create mode 100644 node_modules/mongodb/lib/mongodb/connection/connection_utils.js create mode 100644 node_modules/mongodb/lib/mongodb/connection/repl_set_servers.js create mode 100644 node_modules/mongodb/lib/mongodb/connection/server.js create mode 100644 node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js create mode 100644 node_modules/mongodb/lib/mongodb/connection/strategies/statistics_strategy.js create mode 100644 node_modules/mongodb/lib/mongodb/cursor.js create mode 100644 node_modules/mongodb/lib/mongodb/cursorstream.js create mode 100644 node_modules/mongodb/lib/mongodb/db.js create mode 100644 node_modules/mongodb/lib/mongodb/gridfs/chunk.js create mode 100644 node_modules/mongodb/lib/mongodb/gridfs/grid.js create mode 100644 node_modules/mongodb/lib/mongodb/gridfs/gridstore.js create mode 100644 node_modules/mongodb/lib/mongodb/gridfs/readstream.js create mode 100644 node_modules/mongodb/lib/mongodb/index.js create mode 100644 node_modules/mongodb/lib/mongodb/responses/mongo_reply.js create mode 100644 node_modules/mongodb/lib/mongodb/utils.js create mode 100644 node_modules/mongodb/node_modules/bson/.travis.yml create mode 100644 node_modules/mongodb/node_modules/bson/Makefile create mode 100644 node_modules/mongodb/node_modules/bson/README create mode 100644 node_modules/mongodb/node_modules/bson/ext/Makefile create mode 100644 node_modules/mongodb/node_modules/bson/ext/bson.cc create mode 100644 node_modules/mongodb/node_modules/bson/ext/bson.h create mode 100644 node_modules/mongodb/node_modules/bson/ext/index.js create mode 100644 node_modules/mongodb/node_modules/bson/ext/wscript create mode 100644 node_modules/mongodb/node_modules/bson/install.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/binary.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/binary_parser.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/bson.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/code.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/db_ref.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/double.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/float_parser.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/index.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/long.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/max_key.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/min_key.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/objectid.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/symbol.js create mode 100644 node_modules/mongodb/node_modules/bson/lib/bson/timestamp.js create mode 100755 node_modules/mongodb/node_modules/bson/package.json create mode 100644 node_modules/mongodb/node_modules/bson/test/browser/bson_test.js create mode 100644 node_modules/mongodb/node_modules/bson/test/browser/nodeunit.js create mode 100644 node_modules/mongodb/node_modules/bson/test/browser/suite2.js create mode 100644 node_modules/mongodb/node_modules/bson/test/browser/suite3.js create mode 100644 node_modules/mongodb/node_modules/bson/test/browser/test.html create mode 100644 node_modules/mongodb/node_modules/bson/test/node/bson_array_test.js create mode 100644 node_modules/mongodb/node_modules/bson/test/node/bson_parser_comparision_test.js create mode 100644 node_modules/mongodb/node_modules/bson/test/node/bson_test.js create mode 100644 node_modules/mongodb/node_modules/bson/test/node/bson_typed_array_test.js create mode 100644 node_modules/mongodb/node_modules/bson/test/node/data/test_gs_weird_bug.png create mode 100644 node_modules/mongodb/node_modules/bson/test/node/test_full_bson.js create mode 100644 node_modules/mongodb/node_modules/bson/test/node/tools/utils.js create mode 100644 node_modules/mongodb/node_modules/bson/tools/gleak.js create mode 100644 node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/MIT.LICENSE create mode 100644 node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine-html.js create mode 100644 node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine.css create mode 100644 node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine.js create mode 100644 node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine_favicon.png create mode 100755 node_modules/mongodb/package.json create mode 100644 node_modules/mongoose/.npmignore create mode 100644 node_modules/mongoose/.travis.yml create mode 100644 node_modules/mongoose/History.md create mode 100644 node_modules/mongoose/Makefile create mode 100644 node_modules/mongoose/README.md create mode 100644 node_modules/mongoose/benchmarks/clone.js create mode 100644 node_modules/mongoose/benchmarks/index.js create mode 100644 node_modules/mongoose/benchmarks/mem.js create mode 100644 node_modules/mongoose/docs/defaults.md create mode 100644 node_modules/mongoose/docs/embedded-documents.md create mode 100644 node_modules/mongoose/docs/errors.md create mode 100644 node_modules/mongoose/docs/finding-documents.md create mode 100644 node_modules/mongoose/docs/getters-setters.md create mode 100644 node_modules/mongoose/docs/indexes.md create mode 100644 node_modules/mongoose/docs/methods-statics.md create mode 100644 node_modules/mongoose/docs/middleware.md create mode 100644 node_modules/mongoose/docs/migration-guide.md create mode 100644 node_modules/mongoose/docs/model-definition.md create mode 100644 node_modules/mongoose/docs/plugins.md create mode 100644 node_modules/mongoose/docs/populate.md create mode 100644 node_modules/mongoose/docs/query.md create mode 100644 node_modules/mongoose/docs/querystream.md create mode 100644 node_modules/mongoose/docs/schematypes.md create mode 100644 node_modules/mongoose/docs/validation.md create mode 100644 node_modules/mongoose/docs/virtuals.md create mode 100644 node_modules/mongoose/examples/schema.js create mode 100644 node_modules/mongoose/index.js create mode 100644 node_modules/mongoose/lib/collection.js create mode 100644 node_modules/mongoose/lib/connection.js create mode 100644 node_modules/mongoose/lib/document.js create mode 100644 node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js create mode 100644 node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js create mode 100644 node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js create mode 100644 node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js create mode 100644 node_modules/mongoose/lib/error.js create mode 100644 node_modules/mongoose/lib/index.js create mode 100644 node_modules/mongoose/lib/model.js create mode 100644 node_modules/mongoose/lib/namedscope.js create mode 100644 node_modules/mongoose/lib/promise.js create mode 100644 node_modules/mongoose/lib/query.js create mode 100644 node_modules/mongoose/lib/querystream.js create mode 100644 node_modules/mongoose/lib/schema.js create mode 100644 node_modules/mongoose/lib/schema/array.js create mode 100644 node_modules/mongoose/lib/schema/boolean.js create mode 100644 node_modules/mongoose/lib/schema/buffer.js create mode 100644 node_modules/mongoose/lib/schema/date.js create mode 100644 node_modules/mongoose/lib/schema/documentarray.js create mode 100644 node_modules/mongoose/lib/schema/index.js create mode 100644 node_modules/mongoose/lib/schema/mixed.js create mode 100644 node_modules/mongoose/lib/schema/number.js create mode 100644 node_modules/mongoose/lib/schema/objectid.js create mode 100644 node_modules/mongoose/lib/schema/string.js create mode 100644 node_modules/mongoose/lib/schemadefault.js create mode 100644 node_modules/mongoose/lib/schematype.js create mode 100644 node_modules/mongoose/lib/statemachine.js create mode 100644 node_modules/mongoose/lib/types/array.js create mode 100644 node_modules/mongoose/lib/types/buffer.js create mode 100644 node_modules/mongoose/lib/types/documentarray.js create mode 100644 node_modules/mongoose/lib/types/embedded.js create mode 100644 node_modules/mongoose/lib/types/index.js create mode 100644 node_modules/mongoose/lib/types/number.js create mode 100644 node_modules/mongoose/lib/types/objectid.js create mode 100644 node_modules/mongoose/lib/utils.js create mode 100644 node_modules/mongoose/lib/virtualtype.js create mode 100644 node_modules/mongoose/package.json create mode 100644 node_modules/mongoose/support/expresso/.gitmodules create mode 100644 node_modules/mongoose/support/expresso/.npmignore create mode 100644 node_modules/mongoose/support/expresso/History.md create mode 100644 node_modules/mongoose/support/expresso/Makefile create mode 100644 node_modules/mongoose/support/expresso/Readme.md create mode 100755 node_modules/mongoose/support/expresso/bin/expresso create mode 100644 node_modules/mongoose/support/expresso/docs/api.html create mode 100644 node_modules/mongoose/support/expresso/docs/index.html create mode 100644 node_modules/mongoose/support/expresso/docs/index.md create mode 100644 node_modules/mongoose/support/expresso/docs/layout/foot.html create mode 100644 node_modules/mongoose/support/expresso/docs/layout/head.html create mode 100644 node_modules/mongoose/support/expresso/lib/bar.js create mode 100644 node_modules/mongoose/support/expresso/lib/foo.js create mode 100644 node_modules/mongoose/support/expresso/package.json create mode 100644 node_modules/mongoose/support/expresso/test/assert.test.js create mode 100644 node_modules/mongoose/support/expresso/test/async.test.js create mode 100644 node_modules/mongoose/support/expresso/test/bar.test.js create mode 100644 node_modules/mongoose/support/expresso/test/foo.test.js create mode 100644 node_modules/mongoose/support/expresso/test/http.test.js create mode 100644 node_modules/mongoose/support/expresso/test/serial/async.test.js create mode 100644 node_modules/mongoose/support/expresso/test/serial/http.test.js create mode 100644 node_modules/mongoose/test/collection.test.js create mode 100644 node_modules/mongoose/test/common.js create mode 100644 node_modules/mongoose/test/connection.test.js create mode 100644 node_modules/mongoose/test/crash.test.js create mode 100644 node_modules/mongoose/test/document.strict.test.js create mode 100644 node_modules/mongoose/test/document.test.js create mode 100644 node_modules/mongoose/test/drivers/node-mongodb-native/collection.test.js create mode 100755 node_modules/mongoose/test/dropdb.js create mode 100644 node_modules/mongoose/test/index.test.js create mode 100644 node_modules/mongoose/test/model.querying.test.js create mode 100644 node_modules/mongoose/test/model.ref.test.js create mode 100644 node_modules/mongoose/test/model.stream.test.js create mode 100644 node_modules/mongoose/test/model.test.js create mode 100644 node_modules/mongoose/test/model.update.test.js create mode 100644 node_modules/mongoose/test/namedscope.test.js create mode 100644 node_modules/mongoose/test/promise.test.js create mode 100644 node_modules/mongoose/test/query.test.js create mode 100644 node_modules/mongoose/test/schema.onthefly.test.js create mode 100644 node_modules/mongoose/test/schema.test.js create mode 100644 node_modules/mongoose/test/shard.test.js create mode 100644 node_modules/mongoose/test/types.array.test.js create mode 100644 node_modules/mongoose/test/types.buffer.test.js create mode 100644 node_modules/mongoose/test/types.document.test.js create mode 100644 node_modules/mongoose/test/types.documentarray.test.js create mode 100644 node_modules/mongoose/test/types.number.test.js create mode 100644 node_modules/mongoose/test/utils.test.js create mode 100644 node_modules/mongoose/test/zzlast.test.js create mode 100644 node_modules/request/LICENSE create mode 100644 node_modules/request/README.md create mode 100644 node_modules/request/main.js create mode 100644 node_modules/request/mimetypes.js create mode 100644 node_modules/request/oauth.js create mode 100644 node_modules/request/package.json create mode 100644 node_modules/request/tests/googledoodle.png create mode 100755 node_modules/request/tests/run.sh create mode 100644 node_modules/request/tests/server.js create mode 100644 node_modules/request/tests/ssl/test.crt create mode 100644 node_modules/request/tests/ssl/test.key create mode 100644 node_modules/request/tests/test-body.js create mode 100644 node_modules/request/tests/test-cookie.js create mode 100644 node_modules/request/tests/test-cookiejar.js create mode 100644 node_modules/request/tests/test-errors.js create mode 100644 node_modules/request/tests/test-httpModule.js create mode 100644 node_modules/request/tests/test-https.js create mode 100644 node_modules/request/tests/test-oauth.js create mode 100644 node_modules/request/tests/test-pipes.js create mode 100644 node_modules/request/tests/test-proxy.js create mode 100644 node_modules/request/tests/test-redirect.js create mode 100644 node_modules/request/tests/test-timeout.js create mode 100644 node_modules/request/uuid.js create mode 100644 node_modules/request/vendor/cookie/index.js create mode 100644 node_modules/request/vendor/cookie/jar.js create mode 100644 node_modules/should/.gitmodules create mode 100644 node_modules/should/.npmignore create mode 100644 node_modules/should/History.md create mode 100644 node_modules/should/Makefile create mode 100644 node_modules/should/Readme.md create mode 100644 node_modules/should/examples/runner.js create mode 100644 node_modules/should/index.js create mode 100644 node_modules/should/lib/eql.js create mode 100644 node_modules/should/lib/should.js create mode 100644 node_modules/should/package.json create mode 100644 node_modules/should/test/exist.test.js create mode 100644 node_modules/should/test/should.test.js create mode 100644 node_modules/socket.io-client/.npmignore create mode 100644 node_modules/socket.io-client/History.md create mode 100644 node_modules/socket.io-client/Makefile create mode 100644 node_modules/socket.io-client/README.md create mode 100755 node_modules/socket.io-client/bin/builder.js create mode 100644 node_modules/socket.io-client/dist/WebSocketMain.swf create mode 100644 node_modules/socket.io-client/dist/WebSocketMainInsecure.swf create mode 100644 node_modules/socket.io-client/dist/socket.io.js create mode 100644 node_modules/socket.io-client/dist/socket.io.min.js create mode 100644 node_modules/socket.io-client/lib/events.js create mode 100644 node_modules/socket.io-client/lib/io.js create mode 100644 node_modules/socket.io-client/lib/json.js create mode 100644 node_modules/socket.io-client/lib/namespace.js create mode 100644 node_modules/socket.io-client/lib/parser.js create mode 100644 node_modules/socket.io-client/lib/socket.js create mode 100644 node_modules/socket.io-client/lib/transport.js create mode 100644 node_modules/socket.io-client/lib/transports/flashsocket.js create mode 100644 node_modules/socket.io-client/lib/transports/htmlfile.js create mode 100644 node_modules/socket.io-client/lib/transports/jsonp-polling.js create mode 100644 node_modules/socket.io-client/lib/transports/websocket.js create mode 100644 node_modules/socket.io-client/lib/transports/xhr-polling.js create mode 100644 node_modules/socket.io-client/lib/transports/xhr.js create mode 100644 node_modules/socket.io-client/lib/util.js create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/.npmignore create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/README.md create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/WebSocketMain.swf create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/IWebSocketLogger.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/WebSocket.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/WebSocketEvent.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/WebSocketMain.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/WebSocketMainInsecure.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/build.sh create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/adobe/net/proxies/RFC2817Socket.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/gsolo/encryption/MD5.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/Crypto.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/cert/MozillaRootCertificates.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/cert/X509Certificate.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/cert/X509CertificateCollection.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/HMAC.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/IHMAC.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/IHash.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/MAC.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/MD2.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/MD5.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/SHA1.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/SHA224.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/SHA256.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/hash/SHABase.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/prng/ARC4.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/prng/IPRNG.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/prng/Random.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/prng/TLSPRF.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/rsa/RSAKey.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/AESKey.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/BlowFishKey.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/CBCMode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/CFB8Mode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/CFBMode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/CTRMode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/DESKey.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/ECBMode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/ICipher.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/IMode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/IPad.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/IStreamCipher.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/ISymmetricKey.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/IVMode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/NullPad.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/OFBMode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/PKCS5.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/SSLPad.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/SimpleIVMode.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/TLSPad.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/TripleDESKey.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/XTeaKey.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/aeskey.pl create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/symmetric/dump.txt create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/AESKeyTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/ARC4Test.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/BigIntegerTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/BlowFishKeyTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/CBCModeTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/CFB8ModeTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/CFBModeTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/CTRModeTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/DESKeyTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/ECBModeTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/HMACTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/ITestHarness.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/MD2Test.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/MD5Test.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/OFBModeTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/RSAKeyTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/SHA1Test.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/SHA224Test.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/SHA256Test.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/TLSPRFTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/TestCase.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/TripleDESKeyTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tests/XTeaKeyTest.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/BulkCiphers.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/CipherSuites.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/IConnectionState.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/ISecurityParameters.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/KeyExchanges.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/MACs.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/SSLConnectionState.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/SSLEvent.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/SSLSecurityParameters.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSConfig.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSConnectionState.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSEngine.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSError.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSEvent.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSSecurityParameters.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSSocket.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSSocketEvent.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/crypto/tls/TLSTest.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/math/BarrettReduction.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/math/BigInteger.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/math/ClassicReduction.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/math/IReduction.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/math/MontgomeryReduction.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/math/NullReduction.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/math/bi_internal.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/ArrayUtil.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/Base64.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/Hex.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/Memory.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/ByteString.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/DER.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/IAsn1Type.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/Integer.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/OID.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/ObjectIdentifier.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/PEM.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/PrintableString.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/Sequence.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/Set.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/Type.as create mode 100755 node_modules/socket.io-client/lib/vendor/web-socket-js/flash-src/com/hurlant/util/der/UTCTime.as create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/sample.html create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/swfobject.js create mode 100644 node_modules/socket.io-client/lib/vendor/web-socket-js/web_socket.js create mode 100755 node_modules/socket.io-client/node_modules/.bin/uglifyjs create mode 100755 node_modules/socket.io-client/node_modules/.bin/wscat create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/.npmignore create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/Readme.md create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/index.js create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/.npmignore create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/LICENSE create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/README create mode 100755 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/Tokenizer.js create mode 100755 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/ZeParser.js create mode 100755 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/benchmark.html create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/index.js create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/package.json create mode 100755 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/test-parser.html create mode 100755 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/test-tokenizer.html create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/tests.js create mode 100755 node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/unicodecategories.js create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/package.json create mode 100644 node_modules/socket.io-client/node_modules/active-x-obfuscator/test.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/.npmignore create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/README.html create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/README.org create mode 100755 node_modules/socket.io-client/node_modules/uglify-js/bin/uglifyjs create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/docstyle.css create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/lib/object-ast.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/lib/parse-js.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/lib/process.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/lib/squeeze-more.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/package.json create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/package.json~ create mode 100755 node_modules/socket.io-client/node_modules/uglify-js/test/beautify.js create mode 100755 node_modules/socket.io-client/node_modules/uglify-js/test/testparser.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/array1.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/array2.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/array3.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/array4.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/assignment.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/concatstring.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/const.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/empty-blocks.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/forstatement.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/if.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/ifreturn.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/ifreturn2.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue10.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue11.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue13.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue14.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue16.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue17.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue20.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue21.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue25.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue27.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue278.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue28.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue29.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue30.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue34.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue4.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue48.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue50.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue53.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue54.1.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue68.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue69.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/issue9.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/mangle.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/null_string.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/strict-equals.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/var.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/whitespace.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/expected/with.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/array1.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/array2.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/array3.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/array4.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/assignment.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/concatstring.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/const.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/empty-blocks.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/forstatement.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/if.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/ifreturn.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/ifreturn2.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue10.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue11.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue13.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue14.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue16.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue17.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue20.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue21.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue25.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue27.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue278.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue28.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue29.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue30.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue34.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue4.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue48.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue50.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue53.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue54.1.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue68.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue69.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/issue9.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/mangle.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/null_string.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/strict-equals.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/var.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/whitespace.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/compress/test/with.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/test/unit/scripts.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/269.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/app.js create mode 100755 node_modules/socket.io-client/node_modules/uglify-js/tmp/embed-tokens.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/goto.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/goto2.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/hoist.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/instrument.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/instrument2.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/liftvars.js create mode 100755 node_modules/socket.io-client/node_modules/uglify-js/tmp/test.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/uglify-hangs.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/tmp/uglify-hangs2.js create mode 100644 node_modules/socket.io-client/node_modules/uglify-js/uglify-js.js create mode 100644 node_modules/socket.io-client/node_modules/ws/.lock-wscript create mode 100644 node_modules/socket.io-client/node_modules/ws/.npmignore create mode 100644 node_modules/socket.io-client/node_modules/ws/.travis.yml create mode 100644 node_modules/socket.io-client/node_modules/ws/History.md create mode 100644 node_modules/socket.io-client/node_modules/ws/Makefile create mode 100644 node_modules/socket.io-client/node_modules/ws/README.md create mode 100644 node_modules/socket.io-client/node_modules/ws/bench/parser.benchmark.js create mode 100644 node_modules/socket.io-client/node_modules/ws/bench/sender.benchmark.js create mode 100644 node_modules/socket.io-client/node_modules/ws/bench/speed.js create mode 100644 node_modules/socket.io-client/node_modules/ws/bench/util.js create mode 100755 node_modules/socket.io-client/node_modules/ws/bin/wscat create mode 100644 node_modules/socket.io-client/node_modules/ws/binding.gyp create mode 100644 node_modules/socket.io-client/node_modules/ws/build/.wafpickle-7 create mode 100755 node_modules/socket.io-client/node_modules/ws/build/Release/bufferutil.node create mode 100755 node_modules/socket.io-client/node_modules/ws/build/Release/validation.node create mode 100644 node_modules/socket.io-client/node_modules/ws/build/c4che/Release.cache.py create mode 100644 node_modules/socket.io-client/node_modules/ws/build/c4che/build.config.py create mode 100644 node_modules/socket.io-client/node_modules/ws/doc/ws.md create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/fileapi/.npmignore create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/fileapi/package.json create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/fileapi/public/app.js create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/fileapi/public/index.html create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/fileapi/public/uploader.js create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/fileapi/server.js create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/serverstats/package.json create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/serverstats/public/index.html create mode 100644 node_modules/socket.io-client/node_modules/ws/examples/serverstats/server.js create mode 100644 node_modules/socket.io-client/node_modules/ws/index.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/BufferPool.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/BufferUtil.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/BufferUtilWindows.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/ErrorCodes.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/Receiver.hixie.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/Receiver.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/Sender.hixie.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/Sender.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/Validation.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/ValidationWindows.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/WebSocket.js create mode 100644 node_modules/socket.io-client/node_modules/ws/lib/WebSocketServer.js create mode 100644 node_modules/socket.io-client/node_modules/ws/make.bat create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/commander/.npmignore create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/commander/.travis.yml create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/commander/History.md create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/commander/Makefile create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/commander/Readme.md create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/commander/index.js create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/commander/lib/commander.js create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/commander/package.json create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/options/.npmignore create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/options/Makefile create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/options/README.md create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/options/lib/options.js create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/options/package.json create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/options/test/fixtures/test.conf create mode 100644 node_modules/socket.io-client/node_modules/ws/node_modules/options/test/options.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/package.json create mode 100644 node_modules/socket.io-client/node_modules/ws/src/bufferutil.cc create mode 100644 node_modules/socket.io-client/node_modules/ws/src/validation.cc create mode 100644 node_modules/socket.io-client/node_modules/ws/test/BufferPool.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/Receiver.hixie.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/Receiver.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/Sender.hixie.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/Sender.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/Validation.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/WebSocket.integration.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/WebSocket.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/WebSocketServer.test.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/autobahn-server.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/autobahn.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/fixtures/certificate.pem create mode 100644 node_modules/socket.io-client/node_modules/ws/test/fixtures/key.pem create mode 100644 node_modules/socket.io-client/node_modules/ws/test/fixtures/request.pem create mode 100644 node_modules/socket.io-client/node_modules/ws/test/fixtures/textfile create mode 100644 node_modules/socket.io-client/node_modules/ws/test/hybi-common.js create mode 100644 node_modules/socket.io-client/node_modules/ws/test/testserver.js create mode 100644 node_modules/socket.io-client/node_modules/ws/wscript create mode 100644 node_modules/socket.io-client/node_modules/xmlhttprequest/README.md create mode 100644 node_modules/socket.io-client/node_modules/xmlhttprequest/XMLHttpRequest.js create mode 100644 node_modules/socket.io-client/node_modules/xmlhttprequest/autotest.watchr create mode 100644 node_modules/socket.io-client/node_modules/xmlhttprequest/demo.js create mode 100644 node_modules/socket.io-client/node_modules/xmlhttprequest/package.json create mode 100644 node_modules/socket.io-client/node_modules/xmlhttprequest/tests/test-constants.js create mode 100644 node_modules/socket.io-client/node_modules/xmlhttprequest/tests/test-headers.js create mode 100644 node_modules/socket.io-client/node_modules/xmlhttprequest/tests/test-request.js create mode 100644 node_modules/socket.io-client/package.json create mode 100644 node_modules/socket.io-client/test/events.test.js create mode 100644 node_modules/socket.io-client/test/io.test.js create mode 100644 node_modules/socket.io-client/test/node/builder.common.js create mode 100644 node_modules/socket.io-client/test/node/builder.test.js create mode 100644 node_modules/socket.io-client/test/parser.test.js create mode 100644 node_modules/socket.io-client/test/socket.test.js create mode 100644 node_modules/socket.io-client/test/util.test.js create mode 100644 node_modules/socket.io-client/test/worker.js create mode 100644 node_modules/socket.io/.npmignore create mode 100644 node_modules/socket.io/.travis.yml create mode 100644 node_modules/socket.io/History.md create mode 100644 node_modules/socket.io/Makefile create mode 100644 node_modules/socket.io/Readme.md create mode 100644 node_modules/socket.io/benchmarks/decode.bench.js create mode 100644 node_modules/socket.io/benchmarks/encode.bench.js create mode 100644 node_modules/socket.io/benchmarks/runner.js create mode 100644 node_modules/socket.io/examples/chat/app.js create mode 100644 node_modules/socket.io/examples/chat/index.jade create mode 100644 node_modules/socket.io/examples/chat/package.json create mode 100644 node_modules/socket.io/examples/chat/public/stylesheets/mixins.styl create mode 100644 node_modules/socket.io/examples/chat/public/stylesheets/style.css create mode 100644 node_modules/socket.io/examples/chat/public/stylesheets/style.styl create mode 100644 node_modules/socket.io/examples/irc-output/app.js create mode 100644 node_modules/socket.io/examples/irc-output/index.jade create mode 100644 node_modules/socket.io/examples/irc-output/irc.js create mode 100644 node_modules/socket.io/examples/irc-output/package.json create mode 100644 node_modules/socket.io/examples/irc-output/public/stylesheets/style.styl create mode 100644 node_modules/socket.io/index.js create mode 100644 node_modules/socket.io/lib/client.js create mode 100644 node_modules/socket.io/lib/logger.js create mode 100644 node_modules/socket.io/lib/manager.js create mode 100644 node_modules/socket.io/lib/namespace.js create mode 100644 node_modules/socket.io/lib/parser.js create mode 100644 node_modules/socket.io/lib/socket.io.js create mode 100644 node_modules/socket.io/lib/socket.js create mode 100644 node_modules/socket.io/lib/static.js create mode 100644 node_modules/socket.io/lib/store.js create mode 100644 node_modules/socket.io/lib/stores/memory.js create mode 100644 node_modules/socket.io/lib/stores/redis.js create mode 100644 node_modules/socket.io/lib/transport.js create mode 100644 node_modules/socket.io/lib/transports/flashsocket.js create mode 100644 node_modules/socket.io/lib/transports/htmlfile.js create mode 100644 node_modules/socket.io/lib/transports/http-polling.js create mode 100644 node_modules/socket.io/lib/transports/http.js create mode 100644 node_modules/socket.io/lib/transports/index.js create mode 100644 node_modules/socket.io/lib/transports/jsonp-polling.js create mode 100644 node_modules/socket.io/lib/transports/websocket.js create mode 100644 node_modules/socket.io/lib/transports/websocket/default.js create mode 100644 node_modules/socket.io/lib/transports/websocket/hybi-07-12.js create mode 100644 node_modules/socket.io/lib/transports/websocket/hybi-16.js create mode 100644 node_modules/socket.io/lib/transports/websocket/index.js create mode 100644 node_modules/socket.io/lib/transports/xhr-polling.js create mode 100644 node_modules/socket.io/lib/util.js create mode 100644 node_modules/socket.io/package.json create mode 100644 node_modules/socket.io/support/node-websocket-client/LICENSE create mode 100644 node_modules/socket.io/support/node-websocket-client/Makefile create mode 100644 node_modules/socket.io/support/node-websocket-client/README.md create mode 100644 node_modules/socket.io/support/node-websocket-client/examples/client-unix.js create mode 100644 node_modules/socket.io/support/node-websocket-client/examples/client.js create mode 100644 node_modules/socket.io/support/node-websocket-client/examples/server-unix.js create mode 100644 node_modules/socket.io/support/node-websocket-client/lib/websocket.js create mode 100644 node_modules/socket.io/support/node-websocket-client/package.json create mode 100644 node_modules/socket.io/support/node-websocket-client/test/test-basic.js create mode 100644 node_modules/socket.io/support/node-websocket-client/test/test-client-close.js create mode 100644 node_modules/socket.io/support/node-websocket-client/test/test-readonly-attrs.js create mode 100644 node_modules/socket.io/support/node-websocket-client/test/test-ready-state.js create mode 100644 node_modules/socket.io/support/node-websocket-client/test/test-server-close.js create mode 100644 node_modules/socket.io/support/node-websocket-client/test/test-unix-send-fd.js create mode 100644 node_modules/socket.io/support/node-websocket-client/test/test-unix-sockets.js (limited to 'node_modules') diff --git a/node_modules/ejs/.gitmodules b/node_modules/ejs/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/node_modules/ejs/.npmignore b/node_modules/ejs/.npmignore new file mode 100644 index 0000000..020ddac --- /dev/null +++ b/node_modules/ejs/.npmignore @@ -0,0 +1,4 @@ +# ignore any vim files: +*.sw[a-z] +vim/.netrwhist +node_modules diff --git a/node_modules/ejs/History.md b/node_modules/ejs/History.md new file mode 100644 index 0000000..75dc16f --- /dev/null +++ b/node_modules/ejs/History.md @@ -0,0 +1,98 @@ + +0.7.1 / 2012-03-26 +================== + + * Fixed exception when using express in production caused by typo. [slaskis] + +0.7.0 / 2012-03-24 +================== + + * Added newline consumption support (`-%>`) [whoatemydomain] + +0.6.1 / 2011-12-09 +================== + + * Fixed `ejs.renderFile()` + +0.6.0 / 2011-12-09 +================== + + * Changed: you no longer need `{ locals: {} }` + +0.5.0 / 2011-11-20 +================== + + * Added express 3.x support + * Added ejs.renderFile() + * Added 'json' filter + * Fixed tests for 0.5.x + +0.4.3 / 2011-06-20 +================== + + * Fixed stacktraces line number when used multiline js expressions [Octave] + +0.4.2 / 2011-05-11 +================== + + * Added client side support + +0.4.1 / 2011-04-21 +================== + + * Fixed error context + +0.4.0 / 2011-04-21 +================== + + * Added; ported jade's error reporting to ejs. [slaskis] + +0.3.1 / 2011-02-23 +================== + + * Fixed optional `compile()` options + +0.3.0 / 2011-02-14 +================== + + * Added 'json' filter [Yuriy Bogdanov] + * Use exported version of parse function to allow monkey-patching [Anatoliy Chakkaev] + +0.2.1 / 2010-10-07 +================== + + * Added filter support + * Fixed _cache_ option. ~4x performance increase + +0.2.0 / 2010-08-05 +================== + + * Added support for global tag config + * Added custom tag support. Closes #5 + * Fixed whitespace bug. Closes #4 + +0.1.0 / 2010-08-04 +================== + + * Faster implementation [ashleydev] + +0.0.4 / 2010-08-02 +================== + + * Fixed single quotes for content outside of template tags. [aniero] + * Changed; `exports.compile()` now expects only "locals" + +0.0.3 / 2010-07-15 +================== + + * Fixed single quotes + +0.0.2 / 2010-07-09 +================== + + * Fixed newline preservation + +0.0.1 / 2010-07-09 +================== + + * Initial release diff --git a/node_modules/ejs/Makefile b/node_modules/ejs/Makefile new file mode 100644 index 0000000..14b9304 --- /dev/null +++ b/node_modules/ejs/Makefile @@ -0,0 +1,23 @@ + +SRC = $(shell find lib -name "*.js" -type f) +UGLIFY_FLAGS = --no-mangle + +all: ejs.min.js + +test: + @./node_modules/.bin/mocha \ + --ui exports + +ejs.js: $(SRC) + @node support/compile.js $^ + +ejs.min.js: ejs.js + @uglifyjs $(UGLIFY_FLAGS) $< > $@ \ + && du ejs.min.js \ + && du ejs.js + +clean: + rm -f ejs.js + rm -f ejs.min.js + +.PHONY: test \ No newline at end of file diff --git a/node_modules/ejs/Readme.md b/node_modules/ejs/Readme.md new file mode 100644 index 0000000..8e398fd --- /dev/null +++ b/node_modules/ejs/Readme.md @@ -0,0 +1,151 @@ + +# EJS + +Embedded JavaScript templates. + +## Installation + + $ npm install ejs + +## Features + + * Complies with the [Express](http://expressjs.com) view system + * Static caching of intermediate JavaScript + * Unbuffered code for conditionals etc `<% code %>` + * Escapes html by default with `<%= code %>` + * Unescaped buffering with `<%- code %>` + * Supports tag customization + * Filter support for designer-friendly templates + * Client-side support + * Newline slurping with `<% code -%>` or `<% -%>` or `<%= code -%>` or `<%- code -%>` + +## Example + + <% if (user) { %> +

<%= user.name %>

+ <% } %> + +## Usage + + ejs.compile(str, options); + // => Function + + ejs.render(str, options); + // => str + +## Options + + - `cache` Compiled functions are cached, requires `filename` + - `filename` Used by `cache` to key caches + - `scope` Function execution context + - `debug` Output generated function body + - `open` Open tag, defaulting to "<%" + - `close` Closing tag, defaulting to "%>" + - * All others are template-local variables + +## Custom tags + +Custom tags can also be applied globally: + + var ejs = require('ejs'); + ejs.open = '{{'; + ejs.close = '}}'; + +Which would make the following a valid template: + +

{{= title }}

+ +## Filters + +EJS conditionally supports the concept of "filters". A "filter chain" +is a designer friendly api for manipulating data, without writing JavaScript. + +Filters can be applied by supplying the _:_ modifier, so for example if we wish to take the array `[{ name: 'tj' }, { name: 'mape' }, { name: 'guillermo' }]` and output a list of names we can do this simply with filters: + +Template: + +

<%=: users | map:'name' | join %>

+ +Output: + +

Tj, Mape, Guillermo

+ +Render call: + + ejs.render(str, { + users: [ + { name: 'tj' }, + { name: 'mape' }, + { name: 'guillermo' } + ] + }); + +Or perhaps capitalize the first user's name for display: + +

<%=: users | first | capitalize %>

+ +## Filter list + +Currently these filters are available: + + - first + - last + - capitalize + - downcase + - upcase + - sort + - sort_by:'prop' + - size + - length + - plus:n + - minus:n + - times:n + - divided_by:n + - join:'val' + - truncate:n + - truncate_words:n + - replace:pattern,substitution + - prepend:val + - append:val + - map:'prop' + - reverse + - get:'prop' + +## Adding filters + + To add a filter simply add a method to the `.filters` object: + +```js +ejs.filters.last = function(obj) { + return obj[obj.length - 1]; +}; +``` + +## client-side support + + include `./ejs.js` or `./ejs.min.js` and `require("ejs").compile(str)`. + +## 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/ejs/benchmark.js b/node_modules/ejs/benchmark.js new file mode 100644 index 0000000..7b267e1 --- /dev/null +++ b/node_modules/ejs/benchmark.js @@ -0,0 +1,14 @@ + + +var ejs = require('./lib/ejs'), + str = '<% if (foo) { %>

<%= foo %>

<% } %>', + times = 50000; + +console.log('rendering ' + times + ' times'); + +var start = new Date; +while (times--) { + ejs.render(str, { cache: true, filename: 'test', locals: { foo: 'bar' }}); +} + +console.log('took ' + (new Date - start) + 'ms'); \ No newline at end of file diff --git a/node_modules/ejs/ejs.js b/node_modules/ejs/ejs.js new file mode 100644 index 0000000..b0fa93b --- /dev/null +++ b/node_modules/ejs/ejs.js @@ -0,0 +1,567 @@ + +// CommonJS require() + +function require(p){ + if ('fs' == p) return {}; + 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.substr(0, 1)) 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("ejs.js", function(module, exports, require){ + +/*! + * EJS + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('./utils') + , fs = require('fs'); + +/** + * Library version. + */ + +exports.version = '0.6.1'; + +/** + * Filters. + * + * @type Object + */ + +var filters = exports.filters = require('./filters'); + +/** + * Intermediate js cache. + * + * @type Object + */ + +var cache = {}; + +/** + * Clear intermediate js cache. + * + * @api public + */ + +exports.clearCache = function(){ + cache = {}; +}; + +/** + * Translate filtered code into function calls. + * + * @param {String} js + * @return {String} + * @api private + */ + +function filtered(js) { + return js.substr(1).split('|').reduce(function(js, filter){ + var parts = filter.split(':') + , name = parts.shift() + , args = parts.shift() || ''; + if (args) args = ', ' + args; + return 'filters.' + name + '(' + js + args + ')'; + }); +}; + +/** + * Re-throw the given `err` in context to the + * `str` of ejs, `filename`, and `lineno`. + * + * @param {Error} err + * @param {String} str + * @param {String} filename + * @param {String} lineno + * @api private + */ + +function rethrow(err, str, filename, lineno){ + var lines = str.split('\n') + , start = Math.max(lineno - 3, 0) + , end = Math.min(lines.length, lineno + 3); + + // 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 || 'ejs') + ':' + + lineno + '\n' + + context + '\n\n' + + err.message; + + throw err; +} + +/** + * Parse the given `str` of ejs, returning the function body. + * + * @param {String} str + * @return {String} + * @api public + */ + +var parse = exports.parse = function(str, options){ + var options = options || {} + , open = options.open || exports.open || '<%' + , close = options.close || exports.close || '%>'; + + var buf = [ + "var buf = [];" + , "\nwith (locals) {" + , "\n buf.push('" + ]; + + var lineno = 1; + + for (var i = 0, len = str.length; i < len; ++i) { + if (str.slice(i, open.length + i) == open) { + i += open.length + + var prefix, postfix, line = '__stack.lineno=' + lineno; + switch (str.substr(i, 1)) { + case '=': + prefix = "', escape((" + line + ', '; + postfix = ")), '"; + ++i; + break; + case '-': + prefix = "', (" + line + ', '; + postfix = "), '"; + ++i; + break; + default: + prefix = "');" + line + ';'; + postfix = "; buf.push('"; + } + + var end = str.indexOf(close, i) + , js = str.substring(i, end) + , start = i + , n = 0; + + while (~(n = js.indexOf("\n", n))) n++, lineno++; + if (js.substr(0, 1) == ':') js = filtered(js); + buf.push(prefix, js, postfix); + i += end - start + close.length - 1; + + } else if (str.substr(i, 1) == "\\") { + buf.push("\\\\"); + } else if (str.substr(i, 1) == "'") { + buf.push("\\'"); + } else if (str.substr(i, 1) == "\r") { + buf.push(" "); + } else if (str.substr(i, 1) == "\n") { + buf.push("\\n"); + lineno++; + } else { + buf.push(str.substr(i, 1)); + } + } + + buf.push("');\n}\nreturn buf.join('');"); + return buf.join(''); +}; + +/** + * Compile the given `str` of ejs into a `Function`. + * + * @param {String} str + * @param {Object} options + * @return {Function} + * @api public + */ + +var compile = exports.compile = function(str, options){ + options = options || {}; + + var input = JSON.stringify(str) + , filename = options.filename + ? JSON.stringify(options.filename) + : 'undefined'; + + // Adds the fancy stack trace meta info + str = [ + 'var __stack = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };', + rethrow.toString(), + 'try {', + exports.parse(str, options), + '} catch (err) {', + ' rethrow(err, __stack.input, __stack.filename, __stack.lineno);', + '}' + ].join("\n"); + + if (options.debug) console.log(str); + var fn = new Function('locals, filters, escape', str); + return function(locals){ + return fn.call(this, locals, filters, utils.escape); + } +}; + +/** + * Render the given `str` of ejs. + * + * Options: + * + * - `locals` Local variables object + * - `cache` Compiled functions are cached, requires `filename` + * - `filename` Used by `cache` to key caches + * - `scope` Function execution context + * - `debug` Output generated function body + * - `open` Open tag, defaulting to "<%" + * - `close` Closing tag, defaulting to "%>" + * + * @param {String} str + * @param {Object} options + * @return {String} + * @api public + */ + +exports.render = function(str, options){ + var fn + , options = options || {}; + + if (options.cache) { + if (options.filename) { + fn = cache[options.filename] || (cache[options.filename] = compile(str, options)); + } else { + throw new Error('"cache" option requires "filename".'); + } + } else { + fn = compile(str, options); + } + + options.__proto__ = options.locals; + return fn.call(options.scope, options); +}; + +/** + * Render an EJS 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 = {}; + } + + options.filename = path; + + try { + var str = options.cache + ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) + : fs.readFileSync(path, 'utf8'); + + fn(null, exports.render(str, options)); + } catch (err) { + fn(err); + } +}; + +// express support + +exports.__express = exports.renderFile; + +/** + * Expose to require(). + */ + +if (require.extensions) { + require.extensions['.ejs'] = function(module, filename) { + source = require('fs').readFileSync(filename, 'utf-8'); + module._compile(compile(source, {}), filename); + }; +} else if (require.registerExtension) { + require.registerExtension('.ejs', function(src) { + return compile(src, {}); + }); +} + +}); // module: ejs.js + +require.register("filters.js", function(module, exports, require){ + +/*! + * EJS - Filters + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * First element of the target `obj`. + */ + +exports.first = function(obj) { + return obj[0]; +}; + +/** + * Last element of the target `obj`. + */ + +exports.last = function(obj) { + return obj[obj.length - 1]; +}; + +/** + * Capitalize the first letter of the target `str`. + */ + +exports.capitalize = function(str){ + str = String(str); + return str[0].toUpperCase() + str.substr(1, str.length); +}; + +/** + * Downcase the target `str`. + */ + +exports.downcase = function(str){ + return String(str).toLowerCase(); +}; + +/** + * Uppercase the target `str`. + */ + +exports.upcase = function(str){ + return String(str).toUpperCase(); +}; + +/** + * Sort the target `obj`. + */ + +exports.sort = function(obj){ + return Object.create(obj).sort(); +}; + +/** + * Sort the target `obj` by the given `prop` ascending. + */ + +exports.sort_by = function(obj, prop){ + return Object.create(obj).sort(function(a, b){ + a = a[prop], b = b[prop]; + if (a > b) return 1; + if (a < b) return -1; + return 0; + }); +}; + +/** + * Size or length of the target `obj`. + */ + +exports.size = exports.length = function(obj) { + return obj.length; +}; + +/** + * Add `a` and `b`. + */ + +exports.plus = function(a, b){ + return Number(a) + Number(b); +}; + +/** + * Subtract `b` from `a`. + */ + +exports.minus = function(a, b){ + return Number(a) - Number(b); +}; + +/** + * Multiply `a` by `b`. + */ + +exports.times = function(a, b){ + return Number(a) * Number(b); +}; + +/** + * Divide `a` by `b`. + */ + +exports.divided_by = function(a, b){ + return Number(a) / Number(b); +}; + +/** + * Join `obj` with the given `str`. + */ + +exports.join = function(obj, str){ + return obj.join(str || ', '); +}; + +/** + * Truncate `str` to `len`. + */ + +exports.truncate = function(str, len){ + str = String(str); + return str.substr(0, len); +}; + +/** + * Truncate `str` to `n` words. + */ + +exports.truncate_words = function(str, n){ + var str = String(str) + , words = str.split(/ +/); + return words.slice(0, n).join(' '); +}; + +/** + * Replace `pattern` with `substitution` in `str`. + */ + +exports.replace = function(str, pattern, substitution){ + return String(str).replace(pattern, substitution || ''); +}; + +/** + * Prepend `val` to `obj`. + */ + +exports.prepend = function(obj, val){ + return Array.isArray(obj) + ? [val].concat(obj) + : val + obj; +}; + +/** + * Append `val` to `obj`. + */ + +exports.append = function(obj, val){ + return Array.isArray(obj) + ? obj.concat(val) + : obj + val; +}; + +/** + * Map the given `prop`. + */ + +exports.map = function(arr, prop){ + return arr.map(function(obj){ + return obj[prop]; + }); +}; + +/** + * Reverse the given `obj`. + */ + +exports.reverse = function(obj){ + return Array.isArray(obj) + ? obj.reverse() + : String(obj).split('').reverse().join(''); +}; + +/** + * Get `prop` of the given `obj`. + */ + +exports.get = function(obj, prop){ + return obj[prop]; +}; + +/** + * Packs the given `obj` into json string + */ +exports.json = function(obj){ + return JSON.stringify(obj); +}; +}); // module: filters.js + +require.register("utils.js", function(module, exports, require){ + +/*! + * EJS + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html){ + return String(html) + .replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +}; + +}); // module: utils.js diff --git a/node_modules/ejs/ejs.min.js b/node_modules/ejs/ejs.min.js new file mode 100644 index 0000000..b878813 --- /dev/null +++ b/node_modules/ejs/ejs.min.js @@ -0,0 +1,2 @@ +// CommonJS require() +function require(p){if("fs"==p)return{};var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');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]&®||require.modules[index]&&index||orig},require.register=function(path,fn){require.modules[path]=fn},require.relative=function(parent){return function(p){if("."!=p.substr(0,1))return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i> ":" ")+curr+"| "+line}).join("\n");err.path=filename,err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}var parse=exports.parse=function(str,options){var options=options||{},open=options.open||exports.open||"<%",close=options.close||exports.close||"%>",buf=["var buf = [];","\nwith (locals) {","\n buf.push('"],lineno=1;for(var i=0,len=str.length;ib)return 1;if(a/g,">").replace(/"/g,""")}}) \ No newline at end of file diff --git a/node_modules/ejs/examples/client.html b/node_modules/ejs/examples/client.html new file mode 100644 index 0000000..51ce0b4 --- /dev/null +++ b/node_modules/ejs/examples/client.html @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/node_modules/ejs/examples/list.ejs b/node_modules/ejs/examples/list.ejs new file mode 100644 index 0000000..d571330 --- /dev/null +++ b/node_modules/ejs/examples/list.ejs @@ -0,0 +1,7 @@ +<% if (names.length) { %> +
    + <% names.forEach(function(name){ %> +
  • <%= name %>
  • + <% }) %> +
+<% } %> \ No newline at end of file diff --git a/node_modules/ejs/examples/list.js b/node_modules/ejs/examples/list.js new file mode 100644 index 0000000..ec614ed --- /dev/null +++ b/node_modules/ejs/examples/list.js @@ -0,0 +1,14 @@ + +/** + * Module dependencies. + */ + +var ejs = require('../') + , fs = require('fs') + , str = fs.readFileSync(__dirname + '/list.ejs', 'utf8'); + +var ret = ejs.render(str, { + names: ['foo', 'bar', 'baz'] +}); + +console.log(ret); \ No newline at end of file diff --git a/node_modules/ejs/index.js b/node_modules/ejs/index.js new file mode 100644 index 0000000..20bf71a --- /dev/null +++ b/node_modules/ejs/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/ejs'); \ No newline at end of file diff --git a/node_modules/ejs/lib/ejs.js b/node_modules/ejs/lib/ejs.js new file mode 100644 index 0000000..e87b98e --- /dev/null +++ b/node_modules/ejs/lib/ejs.js @@ -0,0 +1,298 @@ + +/*! + * EJS + * Copyright(c) 2012 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('./utils') + , fs = require('fs'); + +/** + * Library version. + */ + +exports.version = '0.7.1'; + +/** + * Filters. + * + * @type Object + */ + +var filters = exports.filters = require('./filters'); + +/** + * Intermediate js cache. + * + * @type Object + */ + +var cache = {}; + +/** + * Clear intermediate js cache. + * + * @api public + */ + +exports.clearCache = function(){ + cache = {}; +}; + +/** + * Translate filtered code into function calls. + * + * @param {String} js + * @return {String} + * @api private + */ + +function filtered(js) { + return js.substr(1).split('|').reduce(function(js, filter){ + var parts = filter.split(':') + , name = parts.shift() + , args = parts.shift() || ''; + if (args) args = ', ' + args; + return 'filters.' + name + '(' + js + args + ')'; + }); +}; + +/** + * Re-throw the given `err` in context to the + * `str` of ejs, `filename`, and `lineno`. + * + * @param {Error} err + * @param {String} str + * @param {String} filename + * @param {String} lineno + * @api private + */ + +function rethrow(err, str, filename, lineno){ + var lines = str.split('\n') + , start = Math.max(lineno - 3, 0) + , end = Math.min(lines.length, lineno + 3); + + // 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 || 'ejs') + ':' + + lineno + '\n' + + context + '\n\n' + + err.message; + + throw err; +} + +/** + * Parse the given `str` of ejs, returning the function body. + * + * @param {String} str + * @return {String} + * @api public + */ + +var parse = exports.parse = function(str, options){ + var options = options || {} + , open = options.open || exports.open || '<%' + , close = options.close || exports.close || '%>'; + + var buf = [ + "var buf = [];" + , "\nwith (locals) {" + , "\n buf.push('" + ]; + + var lineno = 1; + + var consumeEOL = false; + for (var i = 0, len = str.length; i < len; ++i) { + if (str.slice(i, open.length + i) == open) { + i += open.length + + var prefix, postfix, line = '__stack.lineno=' + lineno; + switch (str.substr(i, 1)) { + case '=': + prefix = "', escape((" + line + ', '; + postfix = ")), '"; + ++i; + break; + case '-': + prefix = "', (" + line + ', '; + postfix = "), '"; + ++i; + break; + default: + prefix = "');" + line + ';'; + postfix = "; buf.push('"; + } + + var end = str.indexOf(close, i) + , js = str.substring(i, end) + , start = i + , n = 0; + + if ('-' == js[js.length-1]){ + js = js.substring(0, js.length - 2); + consumeEOL = true; + } + + while (~(n = js.indexOf("\n", n))) n++, lineno++; + if (js.substr(0, 1) == ':') js = filtered(js); + buf.push(prefix, js, postfix); + i += end - start + close.length - 1; + + } else if (str.substr(i, 1) == "\\") { + buf.push("\\\\"); + } else if (str.substr(i, 1) == "'") { + buf.push("\\'"); + } else if (str.substr(i, 1) == "\r") { + buf.push(" "); + } else if (str.substr(i, 1) == "\n") { + if (consumeEOL) { + consumeEOL = false; + } else { + buf.push("\\n"); + lineno++; + } + } else { + buf.push(str.substr(i, 1)); + } + } + + buf.push("');\n}\nreturn buf.join('');"); + return buf.join(''); +}; + +/** + * Compile the given `str` of ejs into a `Function`. + * + * @param {String} str + * @param {Object} options + * @return {Function} + * @api public + */ + +var compile = exports.compile = function(str, options){ + options = options || {}; + + var input = JSON.stringify(str) + , filename = options.filename + ? JSON.stringify(options.filename) + : 'undefined'; + + // Adds the fancy stack trace meta info + str = [ + 'var __stack = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };', + rethrow.toString(), + 'try {', + exports.parse(str, options), + '} catch (err) {', + ' rethrow(err, __stack.input, __stack.filename, __stack.lineno);', + '}' + ].join("\n"); + + if (options.debug) console.log(str); + var fn = new Function('locals, filters, escape', str); + return function(locals){ + return fn.call(this, locals, filters, utils.escape); + } +}; + +/** + * Render the given `str` of ejs. + * + * Options: + * + * - `locals` Local variables object + * - `cache` Compiled functions are cached, requires `filename` + * - `filename` Used by `cache` to key caches + * - `scope` Function execution context + * - `debug` Output generated function body + * - `open` Open tag, defaulting to "<%" + * - `close` Closing tag, defaulting to "%>" + * + * @param {String} str + * @param {Object} options + * @return {String} + * @api public + */ + +exports.render = function(str, options){ + var fn + , options = options || {}; + + if (options.cache) { + if (options.filename) { + fn = cache[options.filename] || (cache[options.filename] = compile(str, options)); + } else { + throw new Error('"cache" option requires "filename".'); + } + } else { + fn = compile(str, options); + } + + options.__proto__ = options.locals; + return fn.call(options.scope, options); +}; + +/** + * Render an EJS 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 = {}; + } + + options.filename = path; + + try { + var str = options.cache + ? cache[key] || (cache[key] = fs.readFileSync(path, 'utf8')) + : fs.readFileSync(path, 'utf8'); + + fn(null, exports.render(str, options)); + } catch (err) { + fn(err); + } +}; + +// express support + +exports.__express = exports.renderFile; + +/** + * Expose to require(). + */ + +if (require.extensions) { + require.extensions['.ejs'] = function(module, filename) { + source = require('fs').readFileSync(filename, 'utf-8'); + module._compile(compile(source, {}), filename); + }; +} else if (require.registerExtension) { + require.registerExtension('.ejs', function(src) { + return compile(src, {}); + }); +} diff --git a/node_modules/ejs/lib/filters.js b/node_modules/ejs/lib/filters.js new file mode 100644 index 0000000..d425c8d --- /dev/null +++ b/node_modules/ejs/lib/filters.js @@ -0,0 +1,198 @@ + +/*! + * EJS - Filters + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * First element of the target `obj`. + */ + +exports.first = function(obj) { + return obj[0]; +}; + +/** + * Last element of the target `obj`. + */ + +exports.last = function(obj) { + return obj[obj.length - 1]; +}; + +/** + * Capitalize the first letter of the target `str`. + */ + +exports.capitalize = function(str){ + str = String(str); + return str[0].toUpperCase() + str.substr(1, str.length); +}; + +/** + * Downcase the target `str`. + */ + +exports.downcase = function(str){ + return String(str).toLowerCase(); +}; + +/** + * Uppercase the target `str`. + */ + +exports.upcase = function(str){ + return String(str).toUpperCase(); +}; + +/** + * Sort the target `obj`. + */ + +exports.sort = function(obj){ + return Object.create(obj).sort(); +}; + +/** + * Sort the target `obj` by the given `prop` ascending. + */ + +exports.sort_by = function(obj, prop){ + return Object.create(obj).sort(function(a, b){ + a = a[prop], b = b[prop]; + if (a > b) return 1; + if (a < b) return -1; + return 0; + }); +}; + +/** + * Size or length of the target `obj`. + */ + +exports.size = exports.length = function(obj) { + return obj.length; +}; + +/** + * Add `a` and `b`. + */ + +exports.plus = function(a, b){ + return Number(a) + Number(b); +}; + +/** + * Subtract `b` from `a`. + */ + +exports.minus = function(a, b){ + return Number(a) - Number(b); +}; + +/** + * Multiply `a` by `b`. + */ + +exports.times = function(a, b){ + return Number(a) * Number(b); +}; + +/** + * Divide `a` by `b`. + */ + +exports.divided_by = function(a, b){ + return Number(a) / Number(b); +}; + +/** + * Join `obj` with the given `str`. + */ + +exports.join = function(obj, str){ + return obj.join(str || ', '); +}; + +/** + * Truncate `str` to `len`. + */ + +exports.truncate = function(str, len){ + str = String(str); + return str.substr(0, len); +}; + +/** + * Truncate `str` to `n` words. + */ + +exports.truncate_words = function(str, n){ + var str = String(str) + , words = str.split(/ +/); + return words.slice(0, n).join(' '); +}; + +/** + * Replace `pattern` with `substitution` in `str`. + */ + +exports.replace = function(str, pattern, substitution){ + return String(str).replace(pattern, substitution || ''); +}; + +/** + * Prepend `val` to `obj`. + */ + +exports.prepend = function(obj, val){ + return Array.isArray(obj) + ? [val].concat(obj) + : val + obj; +}; + +/** + * Append `val` to `obj`. + */ + +exports.append = function(obj, val){ + return Array.isArray(obj) + ? obj.concat(val) + : obj + val; +}; + +/** + * Map the given `prop`. + */ + +exports.map = function(arr, prop){ + return arr.map(function(obj){ + return obj[prop]; + }); +}; + +/** + * Reverse the given `obj`. + */ + +exports.reverse = function(obj){ + return Array.isArray(obj) + ? obj.reverse() + : String(obj).split('').reverse().join(''); +}; + +/** + * Get `prop` of the given `obj`. + */ + +exports.get = function(obj, prop){ + return obj[prop]; +}; + +/** + * Packs the given `obj` into json string + */ +exports.json = function(obj){ + return JSON.stringify(obj); +}; \ No newline at end of file diff --git a/node_modules/ejs/lib/utils.js b/node_modules/ejs/lib/utils.js new file mode 100644 index 0000000..8d569d6 --- /dev/null +++ b/node_modules/ejs/lib/utils.js @@ -0,0 +1,23 @@ + +/*! + * EJS + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html){ + return String(html) + .replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +}; + \ No newline at end of file diff --git a/node_modules/ejs/package.json b/node_modules/ejs/package.json new file mode 100644 index 0000000..e6e59df --- /dev/null +++ b/node_modules/ejs/package.json @@ -0,0 +1,32 @@ +{ + "name": "ejs", + "description": "Embedded JavaScript templates", + "version": "0.7.1", + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "keywords": [ + "template", + "engine", + "ejs" + ], + "devDependencies": { + "mocha": "*" + }, + "main": "./lib/ejs.js", + "_id": "ejs@0.7.1", + "dependencies": {}, + "optionalDependencies": {}, + "engines": { + "node": "*" + }, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "76261bc960735bee0897c8a4278ab852a4627eb2" + }, + "_from": "ejs@0.7.1" +} diff --git a/node_modules/ejs/support/compile.js b/node_modules/ejs/support/compile.js new file mode 100644 index 0000000..95cafc8 --- /dev/null +++ b/node_modules/ejs/support/compile.js @@ -0,0 +1,174 @@ + +/** + * Module dependencies. + */ + +var fs = require('fs'); + +/** + * Arguments. + */ + +var args = process.argv.slice(2) + , pending = args.length + , files = {}; + +console.log(''); + +// parse arguments + +args.forEach(function(file){ + var mod = file.replace('lib/', ''); + fs.readFile(file, 'utf8', function(err, js){ + if (err) throw err; + console.log(' \033[90mcompile : \033[0m\033[36m%s\033[0m', file); + files[file] = parse(js); + --pending || compile(); + }); +}); + +/** + * Parse the given `js`. + */ + +function parse(js) { + return parseInheritance(parseConditionals(js)); +} + +/** + * Parse __proto__. + */ + +function parseInheritance(js) { + return js + .replace(/^ *(\w+)\.prototype\.__proto__ * = *(\w+)\.prototype *;?/gm, function(_, child, parent){ + return child + '.prototype = new ' + parent + ';\n' + + child + '.prototype.constructor = '+ child + ';\n'; + }); +} + +/** + * Parse the given `js`, currently supporting: + * + * 'if' ['node' | 'browser'] + * 'end' + * + */ + +function parseConditionals(js) { + var lines = js.split('\n') + , len = lines.length + , buffer = true + , browser = false + , buf = [] + , line + , cond; + + for (var i = 0; i < len; ++i) { + line = lines[i]; + if (/^ *\/\/ *if *(node|browser)/gm.exec(line)) { + cond = RegExp.$1; + buffer = browser = 'browser' == cond; + } else if (/^ *\/\/ *end/.test(line)) { + buffer = true; + browser = false; + } else if (browser) { + buf.push(line.replace(/^( *)\/\//, '$1')); + } else if (buffer) { + buf.push(line); + } + } + + return buf.join('\n'); +} + +/** + * Compile the files. + */ + +function compile() { + var buf = ''; + buf += '\n// CommonJS require()\n\n'; + buf += browser.require + '\n\n'; + buf += 'require.modules = {};\n\n'; + buf += 'require.resolve = ' + browser.resolve + ';\n\n'; + buf += 'require.register = ' + browser.register + ';\n\n'; + buf += 'require.relative = ' + browser.relative + ';\n\n'; + args.forEach(function(file){ + var js = files[file]; + file = file.replace('lib/', ''); + buf += '\nrequire.register("' + file + '", function(module, exports, require){\n'; + buf += js; + buf += '\n}); // module: ' + file + '\n'; + }); + fs.writeFile('ejs.js', buf, function(err){ + if (err) throw err; + console.log(' \033[90m create : \033[0m\033[36m%s\033[0m', 'ejs.js'); + console.log(); + }); +} + +// refactored version of weepy's +// https://github.com/weepy/brequire/blob/master/browser/brequire.js + +var browser = { + + /** + * Require a module. + */ + + require: function require(p){ + if ('fs' == p) return {}; + 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; + }, + + /** + * Resolve module path. + */ + + resolve: function(path){ + var orig = path + , reg = path + '.js' + , index = path + '/index.js'; + return require.modules[reg] && reg + || require.modules[index] && index + || orig; + }, + + /** + * Return relative require(). + */ + + relative: function(parent) { + return function(p){ + if ('.' != p.substr(0, 1)) 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('/')); + }; + }, + + /** + * Register a module. + */ + + register: function(path, fn){ + require.modules[path] = fn; + } +}; \ No newline at end of file diff --git a/node_modules/ejs/test/ejs.test.js b/node_modules/ejs/test/ejs.test.js new file mode 100644 index 0000000..54b8d2d --- /dev/null +++ b/node_modules/ejs/test/ejs.test.js @@ -0,0 +1,329 @@ + +/** + * Module dependencies. + */ + +var ejs = require('../') + , assert = require('assert'); + +module.exports = { + 'test .version': function(){ + assert.ok(/^\d+\.\d+\.\d+$/.test(ejs.version), 'Test .version format'); + }, + + 'test html': function(){ + assert.equal('

yay

', ejs.render('

yay

')); + }, + + 'test renderFile': function(){ + var html = '

tj

', + str = '

<%= name %>

', + options = { name: 'tj', open: '{', close: '}' }; + + ejs.renderFile(__dirname + '/fixtures/user.ejs', options, function(err, res){ + assert.ok(!err); + assert.equal(res, html); + }) + }, + + 'test buffered code': function(){ + var html = '

tj

', + str = '

<%= name %>

', + locals = { name: 'tj' }; + assert.equal(html, ejs.render(str, { locals: locals })); + }, + + 'test unbuffered code': function(){ + var html = '

tj

', + str = '<% if (name) { %>

<%= name %>

<% } %>', + locals = { name: 'tj' }; + assert.equal(html, ejs.render(str, { locals: locals })); + }, + + 'test `scope` option': function(){ + var html = '

tj

', + str = '

<%= this %>

'; + assert.equal(html, ejs.render(str, { scope: 'tj' })); + }, + + 'test escaping': function(){ + assert.equal('<script>', ejs.render('<%= " + + + +
+

{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/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/.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..cfa2462 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/Readme.md @@ -0,0 +1,303 @@ +# Formidable + +[![Build Status](https://secure.travis-ci.org/felixge/node-formidable.png?branch=master)](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.9 + +* Emit progress when content length header parsed (Tim Koschützki) +* Fix Readme syntax due to GitHub changes (goob) +* Replace references to old 'sys' module in Readme with 'util' (Peter Sugihara) + +### v1.0.8 + +* Strip potentially unsafe characters when using `keepExtensions: true`. +* Switch to utest / urun for testing +* Add travis build + +### v1.0.7 + +* Remove file from package that was causing problems when installing on windows. (#102) +* Fix typos in Readme (Jason Davies). + +### 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'), + + util = require('util'); + + 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(util.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..b1e2bfb --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/lib/incoming_form.js @@ -0,0 +1,378 @@ +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); + this.emit('progress', this.bytesReceived, this.bytesExpected); + } +}; + +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..97e98fb --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/package.json @@ -0,0 +1,32 @@ +{ + "name": "formidable", + "version": "1.0.9", + "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": "*" + }, + "_id": "formidable@1.0.9", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "50cb64f10788073052dda301e3ef0ba99cbb75e9" + }, + "_from": "formidable@1.0.x" +} 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/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/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..b64df8b --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-incoming-form.js @@ -0,0 +1,726 @@ +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.bytesReceived, null); + assert.strictEqual(form.bytesExpected, null); + + form.headers['content-length'] = '8'; + gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) { + assert.equal(event, 'progress'); + assert.equal(bytesReceived, 0); + assert.equal(bytesExpected, 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'; + gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) { + assert.equal(event, 'progress'); + assert.equal(bytesReceived, 0); + assert.equal(bytesExpected, 8); + }); + 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..268592d --- /dev/null +++ b/node_modules/express/node_modules/connect/package.json @@ -0,0 +1,49 @@ +{ + "name": "connect", + "version": "1.8.6", + "description": "High performance middleware framework", + "keywords": [ + "framework", + "web", + "middleware", + "connect", + "rack" + ], + "repository": { + "type": "git", + "url": "git://github.com/senchalabs/connect.git" + }, + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "url": "http://tjholowaychuk.com" + }, + "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" + }, + "_id": "connect@1.8.6", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "0c455688d8df01c210eb2e53591436a50e0c506c" + }, + "_from": "connect@1.x" +} 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..a26fd79 --- /dev/null +++ b/node_modules/express/node_modules/mime/package.json @@ -0,0 +1,40 @@ +{ + "author": { + "name": "Robert Kieffer", + "email": "robert@broofa.com", + "url": "http://github.com/broofa" + }, + "contributors": [ + { + "name": "Benjamin Thomas", + "email": "benjamin@benjaminthomas.org", + "url": "http://github.com/bentomas" + } + ], + "dependencies": {}, + "description": "A comprehensive library for mime-type mapping", + "devDependencies": { + "async_testing": "" + }, + "keywords": [ + "util", + "mime" + ], + "main": "mime.js", + "name": "mime", + "repository": { + "url": "git://github.com/bentomas/node-mime.git", + "type": "git" + }, + "version": "1.2.4", + "_id": "mime@1.2.4", + "optionalDependencies": {}, + "engines": { + "node": "*" + }, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "mime@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..b4dd75f --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/README.markdown @@ -0,0 +1,54 @@ +mkdirp +====== + +Like `mkdir -p`, but in node.js! + +example +======= + +pow.js +------ + var mkdirp = require('mkdirp'); + + mkdirp('/tmp/foo/bar/baz', 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`. + +If `mode` isn't specified, it defaults to `0777 & (~process.umask())`. + +mkdirp.sync(dir, mode) +---------------------- + +Synchronously create a new directory and any necessary subdirectories at `dir` +with octal permission string `mode`. + +If `mode` isn't specified, it defaults to `0777 & (~process.umask())`. + +install +======= + +With [npm](http://npmjs.org) do: + + npm install mkdirp + +license +======= + +MIT/X11 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..e692421 --- /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', 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..25f43ad --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/index.js @@ -0,0 +1,79 @@ +var path = require('path'); +var fs = require('fs'); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, mode, f) { + if (typeof mode === 'function' || mode === undefined) { + f = mode; + mode = 0777 & (~process.umask()); + } + + 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 cb(); + }); + break; + + default: + cb(er); + break; + } + }); +} + +mkdirP.sync = function sync (p, mode) { + if (mode === undefined) { + mode = 0777 & (~process.umask()); + } + + 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 return null; + break; + default : + throw err0 + break; + } + } + + return null; +}; 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..2c6fc93 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/package.json @@ -0,0 +1,40 @@ +{ + "name": "mkdirp", + "description": "Recursively mkdir, like `mkdir -p`", + "version": "0.3.0", + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "main": "./index", + "keywords": [ + "mkdir", + "directory" + ], + "repository": { + "type": "git", + "url": "git://github.com/substack/node-mkdirp.git" + }, + "scripts": { + "test": "tap test/*.js" + }, + "devDependencies": { + "tap": "0.0.x" + }, + "license": "MIT/X11", + "engines": { + "node": "*" + }, + "_id": "mkdirp@0.3.0", + "dependencies": {}, + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "b3f5fb223a8091bc45c59beba087473cf1db44b2" + }, + "_from": "mkdirp@0.3.0" +} diff --git a/node_modules/express/node_modules/mkdirp/test/chmod.js b/node_modules/express/node_modules/mkdirp/test/chmod.js new file mode 100644 index 0000000..520dcb8 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/test/chmod.js @@ -0,0 +1,38 @@ +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.end(); + }); + }); +}); diff --git a/node_modules/express/node_modules/mkdirp/test/clobber.js b/node_modules/express/node_modules/mkdirp/test/clobber.js new file mode 100644 index 0000000..0eb7099 --- /dev/null +++ b/node_modules/express/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/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/perm.js b/node_modules/express/node_modules/mkdirp/test/perm.js new file mode 100644 index 0000000..23a7abb --- /dev/null +++ b/node_modules/express/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/express/node_modules/mkdirp/test/perm_sync.js b/node_modules/express/node_modules/mkdirp/test/perm_sync.js new file mode 100644 index 0000000..f685f60 --- /dev/null +++ b/node_modules/express/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/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/mkdirp/test/sync.js b/node_modules/express/node_modules/mkdirp/test/sync.js new file mode 100644 index 0000000..e0e389d --- /dev/null +++ b/node_modules/express/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/express/node_modules/mkdirp/test/umask.js b/node_modules/express/node_modules/mkdirp/test/umask.js new file mode 100644 index 0000000..64ccafe --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/test/umask.js @@ -0,0 +1,28 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('implicit mode from umask', 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, 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, 0777 & (~process.umask())); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/node_modules/express/node_modules/mkdirp/test/umask_sync.js b/node_modules/express/node_modules/mkdirp/test/umask_sync.js new file mode 100644 index 0000000..83cba56 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/test/umask_sync.js @@ -0,0 +1,27 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('umask sync modes', 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); + 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, (0777 & (~process.umask()))); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) +}); 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/.npmignore b/node_modules/express/node_modules/qs/.npmignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/node_modules/express/node_modules/qs/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/node_modules/express/node_modules/qs/.travis.yml b/node_modules/express/node_modules/qs/.travis.yml new file mode 100644 index 0000000..2c0a8f6 --- /dev/null +++ b/node_modules/express/node_modules/qs/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.6 + - 0.4 \ No newline at end of file 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..3eaf7df --- /dev/null +++ b/node_modules/express/node_modules/qs/History.md @@ -0,0 +1,73 @@ + +0.4.2 / 2012-02-08 +================== + + * Fixed: ensure objects are created when appropriate not arrays [aheckmann] + +0.4.1 / 2012-01-26 +================== + + * Fixed stringify()ing numbers. Closes #23 + +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..db0d145 --- /dev/null +++ b/node_modules/express/node_modules/qs/Readme.md @@ -0,0 +1,54 @@ +# 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 + +```js +var qs = require('qs'); + +qs.parse('user[name][first]=Tobi&user[email]=tobi@learnboost.com'); +// => { user: { name: { first: 'Tobi' }, email: 'tobi@learnboost.com' } } + +qs.stringify({ user: { name: 'Tobi', email: 'tobi@learnboost.com' }}) +// => user[name]=Tobi&user[email]=tobi%40learnboost.com +``` + +## 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..27617b7 --- /dev/null +++ b/node_modules/express/node_modules/qs/examples.js @@ -0,0 +1,51 @@ + +/** + * 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) + +var str = qs.stringify({ user: { name: 'Tobi', email: 'tobi@learnboost.com' }}); +console.log(str); \ No newline at end of file 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..6c72712 --- /dev/null +++ b/node_modules/express/node_modules/qs/lib/querystring.js @@ -0,0 +1,264 @@ + +/*! + * querystring + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Library version. + */ + +exports.version = '0.4.2'; + +/** + * Object#toString() ref for stringify(). + */ + +var toString = Object.prototype.toString; + +/** + * Cache non-integer test regexp. + */ + +var isint = /^[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 (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key); + parse(parts, obj, part, val); + // key + } else { + if (!isint.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 (!isint.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 + '=' + obj; + } +}; + +/** + * 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..20672e8 --- /dev/null +++ b/node_modules/express/node_modules/qs/package.json @@ -0,0 +1,33 @@ +{ + "name": "qs", + "description": "querystring parser", + "version": "0.4.2", + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/node-querystring.git" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "url": "http://tjholowaychuk.com" + }, + "main": "index", + "engines": { + "node": "*" + }, + "_id": "qs@0.4.2", + "dependencies": {}, + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "7d6f7309dd7f451c98d8a8565bb025ed1965ccbb" + }, + "_from": "qs@0.4.x" +} 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..f219e27 --- /dev/null +++ b/node_modules/express/node_modules/qs/test/parse.js @@ -0,0 +1,167 @@ + +/** + * 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'] }}}); + + var o = qs.parse('existing[fcbaebfecc][name][last]=tj') + o.should.eql({ existing: { 'fcbaebfecc': { name: { last: 'tj' }}}}) + Array.isArray(o.existing).should.be.false; + }, + + '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 semi-parsed': function(){ + qs.parse({ 'user[name]': 'tobi' }) + .should.eql({ user: { name: 'tobi' }}); + + qs.parse({ 'user[name]': 'tobi', 'user[email][main]': 'tobi@lb.com' }) + .should.eql({ user: { name: 'tobi', email: { main: 'tobi@lb.com' } }}); + } + + // '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..c2195cb --- /dev/null +++ b/node_modules/express/node_modules/qs/test/stringify.js @@ -0,0 +1,103 @@ + +/** + * Module dependencies. + */ + +var qs = require('../') + , should = require('should') + , str_identities = { + 'basics': [ + { str: 'foo=bar', obj: {'foo' : 'bar'}}, + { str: 'foo=%22bar%22', obj: {'foo' : '\"bar\"'}}, + { str: 'foo=', obj: {'foo': ''}}, + { str: 'foo=1&bar=2', obj: {'foo' : '1', 'bar' : '2'}}, + { str: 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', obj: {'my weird field': "q1!2\"'w$5&7/z8)?"}}, + { str: 'foo%3Dbaz=bar', obj: {'foo=baz': 'bar'}}, + { str: 'foo=bar&bar=baz', obj: {foo: 'bar', bar: 'baz'}} + ], + 'escaping': [ + { str: 'foo=foo%20bar', obj: {foo: 'foo bar'}}, + { str: 'cht=p3&chd=t%3A60%2C40&chs=250x100&chl=Hello%7CWorld', obj: { + cht: 'p3' + , chd: 't:60,40' + , chs: '250x100' + , chl: 'Hello|World' + }} + ], + 'nested': [ + { str: 'foo[]=bar&foo[]=quux', obj: {'foo' : ['bar', 'quux']}}, + { str: 'foo[]=bar', obj: {foo: ['bar']}}, + { str: 'foo[]=1&foo[]=2', obj: {'foo' : ['1', '2']}}, + { str: 'foo=bar&baz[]=1&baz[]=2&baz[]=3', obj: {'foo' : 'bar', 'baz' : ['1', '2', '3']}}, + { str: 'foo[]=bar&baz[]=1&baz[]=2&baz[]=3', obj: {'foo' : ['bar'], 'baz' : ['1', '2', '3']}}, + { str: 'x[y][z]=1', obj: {'x' : {'y' : {'z' : '1'}}}}, + { str: 'x[y][z][]=1', obj: {'x' : {'y' : {'z' : ['1']}}}}, + { str: 'x[y][z]=2', obj: {'x' : {'y' : {'z' : '2'}}}}, + { str: 'x[y][z][]=1&x[y][z][]=2', obj: {'x' : {'y' : {'z' : ['1', '2']}}}}, + { str: 'x[y][][z]=1', obj: {'x' : {'y' : [{'z' : '1'}]}}}, + { str: 'x[y][][z][]=1', obj: {'x' : {'y' : [{'z' : ['1']}]}}}, + { str: 'x[y][][z]=1&x[y][][w]=2', obj: {'x' : {'y' : [{'z' : '1', 'w' : '2'}]}}}, + { str: 'x[y][][v][w]=1', obj: {'x' : {'y' : [{'v' : {'w' : '1'}}]}}}, + { str: 'x[y][][z]=1&x[y][][v][w]=2', obj: {'x' : {'y' : [{'z' : '1', 'v' : {'w' : '2'}}]}}}, + { str: 'x[y][][z]=1&x[y][][z]=2', obj: {'x' : {'y' : [{'z' : '1'}, {'z' : '2'}]}}}, + { str: 'x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3', obj: {'x' : {'y' : [{'z' : '1', 'w' : 'a'}, {'z' : '2', 'w' : '3'}]}}}, + { str: 'user[name][first]=tj&user[name][last]=holowaychuk', obj: { user: { name: { first: 'tj', last: 'holowaychuk' }}}} + ], + 'errors': [ + { obj: 'foo=bar', message: 'stringify expects an object' }, + { obj: ['foo', 'bar'], message: 'stringify expects an object' } + ], + 'numbers': [ + { str: 'limit[]=1&limit[]=2&limit[]=3', obj: { limit: [1, 2, '3'] }}, + { str: 'limit=1', obj: { limit: 1 }} + ] + }; + + +// 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 < str_identities[type].length; i++) { + str = str_identities[type][i].str; + obj = str_identities[type][i].obj; + qs.stringify(obj).should.eql(str); + } +} + +module.exports = { + 'test basics': function() { + test('basics'); + }, + + 'test escaping': function() { + test('escaping'); + }, + + 'test nested': function() { + test('nested'); + }, + + 'test numbers': function(){ + test('numbers'); + }, + + 'test errors': function() { + var obj, message; + for (var i = 0; i < str_identities['errors'].length; i++) { + message = str_identities['errors'][i].message; + obj = str_identities['errors'][i].obj; + err(function(){ qs.stringify(obj) }, 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..6729dce --- /dev/null +++ b/node_modules/express/package.json @@ -0,0 +1,74 @@ +{ + "name": "express", + "description": "Sinatra inspired web development framework", + "version": "2.5.8", + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "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.x", + "mime": "1.2.4", + "qs": "0.4.x", + "mkdirp": "0.3.0" + }, + "devDependencies": { + "connect-form": "0.2.1", + "ejs": "0.4.2", + "expresso": "0.9.2", + "hamljs": "0.6.x", + "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": { + "type": "git", + "url": "git://github.com/visionmedia/express.git" + }, + "main": "index", + "bin": { + "express": "./bin/express" + }, + "scripts": { + "test": "make test", + "prepublish": "npm prune" + }, + "engines": { + "node": ">= 0.4.1 < 0.7.0" + }, + "_id": "express@2.5.8", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "express@2.5.8" +} 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/hooks/.npmignore b/node_modules/hooks/.npmignore new file mode 100644 index 0000000..96e29a8 --- /dev/null +++ b/node_modules/hooks/.npmignore @@ -0,0 +1,2 @@ +**.swp +node_modules diff --git a/node_modules/hooks/Makefile b/node_modules/hooks/Makefile new file mode 100644 index 0000000..1db5d65 --- /dev/null +++ b/node_modules/hooks/Makefile @@ -0,0 +1,9 @@ +test: + @NODE_ENV=test ./node_modules/expresso/bin/expresso \ + $(TESTFLAGS) \ + ./test.js + +test-cov: + @TESTFLAGS=--cov $(MAKE) test + +.PHONY: test test-cov diff --git a/node_modules/hooks/README.md b/node_modules/hooks/README.md new file mode 100644 index 0000000..af90a48 --- /dev/null +++ b/node_modules/hooks/README.md @@ -0,0 +1,306 @@ +hooks +============ + +Add pre and post middleware hooks to your JavaScript methods. + +## Installation + npm install hooks + +## Motivation +Suppose you have a JavaScript object with a `save` method. + +It would be nice to be able to declare code that runs before `save` and after `save`. +For example, you might want to run validation code before every `save`, +and you might want to dispatch a job to a background job queue after `save`. + +One might have an urge to hard code this all into `save`, but that turns out to +couple all these pieces of functionality (validation, save, and job creation) more +tightly than is necessary. For example, what if someone does not want to do background +job creation after the logical save? + +It is nicer to tack on functionality using what we call `pre` and `post` hooks. These +are functions that you define and that you direct to execute before or after particular +methods. + +## Example +We can use `hooks` to add validation and background jobs in the following way: + + var hooks = require('hooks') + , Document = require('./path/to/some/document/constructor'); + + // Add hooks' methods: `hook`, `pre`, and `post` + for (var k in hooks) { + Document[k] = hooks[k]; + } + + // Define a new method that is able to invoke pre and post middleware + Document.hook('save', Document.prototype.save); + + // Define a middleware function to be invoked before 'save' + Document.pre('save', function validate (next) { + // The `this` context inside of `pre` and `post` functions + // is the Document instance + if (this.isValid()) next(); // next() passes control to the next middleware + // or to the target method itself + else next(new Error("Invalid")); // next(error) invokes an error callback + }); + + // Define a middleware function to be invoked after 'save' + Document.post('save', function createJob () { + this.sendToBackgroundQueue(); + }); + +If you already have defined `Document.prototype` methods for which you want pres and posts, +then you do not need to explicitly invoke `Document.hook(...)`. Invoking `Document.pre(methodName, fn)` +or `Document.post(methodName, fn)` will automatically and lazily change `Document.prototype[methodName]` +so that it plays well with `hooks`. An equivalent way to implement the previous example is: + +```javascript +var hooks = require('hooks') + , Document = require('./path/to/some/document/constructor'); + +// Add hooks' methods: `hook`, `pre`, and `post` +for (var k in hooks) { + Document[k] = hooks[k]; +} + +Document.prototype.save = function () { + // ... +}; + +// Define a middleware function to be invoked before 'save' +Document.pre('save', function validate (next) { + // The `this` context inside of `pre` and `post` functions + // is the Document instance + if (this.isValid()) next(); // next() passes control to the next middleware + // or to the target method itself + else next(new Error("Invalid")); // next(error) invokes an error callback +}); + +// Define a middleware function to be invoked after 'save' +Document.post('save', function createJob () { + this.sendToBackgroundQueue(); +}); +``` + +## Pres and Posts as Middleware +We structure pres and posts as middleware to give you maximum flexibility: + +1. You can define **multiple** pres (or posts) for a single method. +2. These pres (or posts) are then executed as a chain of methods. +3. Any functions in this middleware chain can choose to halt the chain's execution by `next`ing an Error from that middleware function. If this occurs, then none of the other middleware in the chain will execute, and the main method (e.g., `save`) will not execute. This is nice, for example, when we don't want a document to save if it is invalid. + +## Defining multiple pres (or posts) +`pre` is chainable, so you can define multiple pres via: + Document.pre('save', function (next, halt) { + console.log("hello"); + }).pre('save', function (next, halt) { + console.log("world"); + }); + +As soon as one pre finishes executing, the next one will be invoked, and so on. + +## Error Handling +You can define a default error handler by passing a 2nd function as the 3rd argument to `hook`: + Document.hook('set', function (path, val) { + this[path] = val; + }, function (err) { + // Handler the error here + console.error(err); + }); + +Then, we can pass errors to this handler from a pre or post middleware function: + Document.pre('set', function (next, path, val) { + next(new Error()); + }); + +If you do not set up a default handler, then `hooks` makes the default handler that just throws the `Error`. + +The default error handler can be over-rided on a per method invocation basis. + +If the main method that you are surrounding with pre and post middleware expects its last argument to be a function +with callback signature `function (error, ...)`, then that callback becomes the error handler, over-riding the default +error handler you may have set up. + +```javascript +Document.hook('save', function (callback) { + // Save logic goes here + ... +}); + +var doc = new Document(); +doc.save( function (err, saved) { + // We can pass err via `next` in any of our pre or post middleware functions + if (err) console.error(err); + + // Rest of callback logic follows ... +}); +``` + +## Mutating Arguments via Middleware +`pre` and `post` middleware can also accept the intended arguments for the method +they augment. This is useful if you want to mutate the arguments before passing +them along to the next middleware and eventually pass a mutated arguments list to +the main method itself. + +As a simple example, let's define a method `set` that just sets a key, value pair. +If we want to namespace the key, we can do so by adding a `pre` middleware hook +that runs before `set`, alters the arguments by namespacing the `key` argument, and passes them onto `set`: + + Document.hook('set', function (key, val) { + this[key] = val; + }); + Document.pre('set', function (next, key, val) { + next('namespace-' + key, val); + }); + var doc = new Document(); + doc.set('hello', 'world'); + console.log(doc.hello); // undefined + console.log(doc['namespace-hello']); // 'world' + +As you can see above, we pass arguments via `next`. + +If you are not mutating the arguments, then you can pass zero arguments +to `next`, and the next middleware function will still have access +to the arguments. + + Document.hook('set', function (key, val) { + this[key] = val; + }); + Document.pre('set', function (next, key, val) { + // I have access to key and val here + next(); // We don't need to pass anything to next + }); + Document.pre('set', function (next, key, val) { + // And I still have access to the original key and val here + next(); + }); + +Finally, you can add arguments that downstream middleware can also see: + + // Note that in the definition of `set`, there is no 3rd argument, options + Document.hook('set', function (key, val) { + // But... + var options = arguments[2]; // ...I have access to an options argument + // because of pre function pre2 (defined below) + console.log(options); // '{debug: true}' + this[key] = val; + }); + Document.pre('set', function pre1 (next, key, val) { + // I only have access to key and val arguments + console.log(arguments.length); // 3 + next(key, val, {debug: true}); + }); + Document.pre('set', function pre2 (next, key, val, options) { + console.log(arguments.length); // 4 + console.log(options); // '{ debug: true}' + next(); + }); + Document.pre('set', function pre3 (next, key, val, options) { + // I still have access to key, val, AND the options argument introduced via the preceding middleware + console.log(arguments.length); // 4 + console.log(options); // '{ debug: true}' + next(); + }); + + var doc = new Document() + doc.set('hey', 'there'); + +## Parallel `pre` middleware + +All middleware up to this point has been "serial" middleware -- i.e., middleware whose logic +is executed as a serial chain. + +Some scenarios call for parallel middleware -- i.e., middleware that can wait for several +asynchronous services at once to respond. + +For instance, you may only want to save a Document only after you have checked +that the Document is valid according to two different remote services. + +We accomplish asynchronous middleware by adding a second kind of flow control callback +(the only flow control callback so far has been `next`), called `done`. + +- `next` passes control to the next middleware in the chain +- `done` keeps track of how many parallel middleware have invoked `done` and passes + control to the target method when ALL parallel middleware have invoked `done`. If + you pass an `Error` to `done`, then the error is handled, and the main method that is + wrapped by pres and posts will not get invoked. + +We declare pre middleware that is parallel by passing a 3rd boolean argument to our `pre` +definition method. + +We illustrate via the parallel validation example mentioned above: + + Document.hook('save', function targetFn (callback) { + // Save logic goes here + // ... + // This only gets run once the two `done`s are both invoked via preOne and preTwo. + }); + + // true marks this as parallel middleware + Document.pre('save', true, function preOne (next, doneOne, callback) { + remoteServiceOne.validate(this.serialize(), function (err, isValid) { + // The code in here will probably be run after the `next` below this block + // and could possibly be run after the console.log("Hola") in `preTwo + if (err) return doneOne(err); + if (isValid) doneOne(); + }); + next(); // Pass control to the next middleware + }); + + // We will suppose that we need 2 different remote services to validate our document + Document.pre('save', true, function preTwo (next, doneTwo, callback) { + remoteServiceTwo.validate(this.serialize(), function (err, isValid) { + if (err) return doneTwo(err); + if (isValid) doneTwo(); + }); + next(); + }); + + // While preOne and preTwo are parallel, preThree is a serial pre middleware + Document.pre('save', function preThree (next, callback) { + next(); + }); + + var doc = new Document(); + doc.save( function (err, doc) { + // Do stuff with the saved doc here... + }); + +In the above example, flow control may happen in the following way: + +(1) doc.save -> (2) preOne --(next)--> (3) preTwo --(next)--> (4) preThree --(next)--> (wait for dones to invoke) -> (5) doneTwo -> (6) doneOne -> (7) targetFn + +So what's happening is that: + +1. You call `doc.save(...)` +2. First, your preOne middleware gets executed. It makes a remote call to the validation service and `next()`s to the preTwo middleware. +3. Now, your preTwo middleware gets executed. It makes a remote call to another validation service and `next()`s to the preThree middleware. +4. Your preThree middleware gets executed. It immediately `next()`s. But nothing else gets executing until both `doneOne` and `doneTwo` are invoked inside the callbacks handling the response from the two valiation services. +5. We will suppose that validation remoteServiceTwo returns a response to us first. In this case, we call `doneTwo` inside the callback to remoteServiceTwo. +6. Some fractions of a second later, remoteServiceOne returns a response to us. In this case, we call `doneOne` inside the callback to remoteServiceOne. +7. `hooks` implementation keeps track of how many parallel middleware has been defined per target function. It detects that both asynchronous pre middlewares (`preOne` and `preTwo`) have finally called their `done` functions (`doneOne` and `doneTwo`), so the implementation finally invokes our `targetFn` (i.e., our core `save` business logic). + +## Removing Pres + +You can remove a particular pre associated with a hook: + + Document.pre('set', someFn); + Document.removePre('set', someFn); + +And you can also remove all pres associated with a hook: + Document.removePre('set'); // Removes all declared `pre`s on the hook 'set' + +## Tests +To run the tests: + make test + +### Contributors +- [Brian Noguchi](https://github.com/bnoguchi) + +### License +MIT License + +--- +### Author +Brian Noguchi diff --git a/node_modules/hooks/hooks.alt.js b/node_modules/hooks/hooks.alt.js new file mode 100644 index 0000000..6ced357 --- /dev/null +++ b/node_modules/hooks/hooks.alt.js @@ -0,0 +1,134 @@ +/** + * Hooks are useful if we want to add a method that automatically has `pre` and `post` hooks. + * For example, it would be convenient to have `pre` and `post` hooks for `save`. + * _.extend(Model, mixins.hooks); + * Model.hook('save', function () { + * console.log('saving'); + * }); + * Model.pre('save', function (next, done) { + * console.log('about to save'); + * next(); + * }); + * Model.post('save', function (next, done) { + * console.log('saved'); + * next(); + * }); + * + * var m = new Model(); + * m.save(); + * // about to save + * // saving + * // saved + */ + +// TODO Add in pre and post skipping options +module.exports = { + /** + * Declares a new hook to which you can add pres and posts + * @param {String} name of the function + * @param {Function} the method + * @param {Function} the error handler callback + */ + hook: function (name, fn, err) { + if (arguments.length === 1 && typeof name === 'object') { + for (var k in name) { // `name` is a hash of hookName->hookFn + this.hook(k, name[k]); + } + return; + } + + if (!err) err = fn; + + var proto = this.prototype || this + , pres = proto._pres = proto._pres || {} + , posts = proto._posts = proto._posts || {}; + pres[name] = pres[name] || []; + posts[name] = posts[name] || []; + + function noop () {} + + proto[name] = function () { + var self = this + , pres = this._pres[name] + , posts = this._posts[name] + , numAsyncPres = 0 + , hookArgs = [].slice.call(arguments) + , preChain = pres.map( function (pre, i) { + var wrapper = function () { + if (arguments[0] instanceof Error) + return err(arguments[0]); + if (numAsyncPres) { + // arguments[1] === asyncComplete + if (arguments.length) + hookArgs = [].slice.call(arguments, 2); + pre.apply(self, + [ preChain[i+1] || allPresInvoked, + asyncComplete + ].concat(hookArgs) + ); + } else { + if (arguments.length) + hookArgs = [].slice.call(arguments); + pre.apply(self, + [ preChain[i+1] || allPresDone ].concat(hookArgs)); + } + }; // end wrapper = function () {... + if (wrapper.isAsync = pre.isAsync) + numAsyncPres++; + return wrapper; + }); // end posts.map(...) + function allPresInvoked () { + if (arguments[0] instanceof Error) + err(arguments[0]); + } + + function allPresDone () { + if (arguments[0] instanceof Error) + return err(arguments[0]); + if (arguments.length) + hookArgs = [].slice.call(arguments); + fn.apply(self, hookArgs); + var postChain = posts.map( function (post, i) { + var wrapper = function () { + if (arguments[0] instanceof Error) + return err(arguments[0]); + if (arguments.length) + hookArgs = [].slice.call(arguments); + post.apply(self, + [ postChain[i+1] || noop].concat(hookArgs)); + }; // end wrapper = function () {... + return wrapper; + }); // end posts.map(...) + if (postChain.length) postChain[0](); + } + + if (numAsyncPres) { + complete = numAsyncPres; + function asyncComplete () { + if (arguments[0] instanceof Error) + return err(arguments[0]); + --complete || allPresDone.call(this); + } + } + (preChain[0] || allPresDone)(); + }; + + return this; + }, + + pre: function (name, fn, isAsync) { + var proto = this.prototype + , pres = proto._pres = proto._pres || {}; + if (fn.isAsync = isAsync) { + this.prototype[name].numAsyncPres++; + } + (pres[name] = pres[name] || []).push(fn); + return this; + }, + post: function (name, fn, isAsync) { + var proto = this.prototype + , posts = proto._posts = proto._posts || {}; + (posts[name] = posts[name] || []).push(fn); + return this; + } +}; diff --git a/node_modules/hooks/hooks.js b/node_modules/hooks/hooks.js new file mode 100644 index 0000000..62d9cd2 --- /dev/null +++ b/node_modules/hooks/hooks.js @@ -0,0 +1,161 @@ +// TODO Add in pre and post skipping options +module.exports = { + /** + * Declares a new hook to which you can add pres and posts + * @param {String} name of the function + * @param {Function} the method + * @param {Function} the error handler callback + */ + hook: function (name, fn, errorCb) { + if (arguments.length === 1 && typeof name === 'object') { + for (var k in name) { // `name` is a hash of hookName->hookFn + this.hook(k, name[k]); + } + return; + } + + var proto = this.prototype || this + , pres = proto._pres = proto._pres || {} + , posts = proto._posts = proto._posts || {}; + pres[name] = pres[name] || []; + posts[name] = posts[name] || []; + + proto[name] = function () { + var self = this + , hookArgs // arguments eventually passed to the hook - are mutable + , lastArg = arguments[arguments.length-1] + , pres = this._pres[name] + , posts = this._posts[name] + , _total = pres.length + , _current = -1 + , _asyncsLeft = proto[name].numAsyncPres + , _next = function () { + if (arguments[0] instanceof Error) { + return handleError(arguments[0]); + } + var _args = Array.prototype.slice.call(arguments) + , currPre + , preArgs; + if (_args.length && !(arguments[0] === null && typeof lastArg === 'function')) + hookArgs = _args; + if (++_current < _total) { + currPre = pres[_current] + if (currPre.isAsync && currPre.length < 2) + throw new Error("Your pre must have next and done arguments -- e.g., function (next, done, ...)"); + if (currPre.length < 1) + throw new Error("Your pre must have a next argument -- e.g., function (next, ...)"); + preArgs = (currPre.isAsync + ? [once(_next), once(_asyncsDone)] + : [once(_next)]).concat(hookArgs); + return currPre.apply(self, preArgs); + } else if (!proto[name].numAsyncPres) { + return _done.apply(self, hookArgs); + } + } + , _done = function () { + var args_ = Array.prototype.slice.call(arguments) + , ret, total_, current_, next_, done_, postArgs; + if (_current === _total) { + ret = fn.apply(self, args_); + total_ = posts.length; + current_ = -1; + next_ = function () { + if (arguments[0] instanceof Error) { + return handleError(arguments[0]); + } + var args_ = Array.prototype.slice.call(arguments, 1) + , currPost + , postArgs; + if (args_.length) hookArgs = args_; + if (++current_ < total_) { + currPost = posts[current_] + if (currPost.length < 1) + throw new Error("Your post must have a next argument -- e.g., function (next, ...)"); + postArgs = [once(next_)].concat(hookArgs); + return currPost.apply(self, postArgs); + } + }; + if (total_) return next_(); + return ret; + } + }; + if (_asyncsLeft) { + function _asyncsDone (err) { + if (err && err instanceof Error) { + return handleError(err); + } + --_asyncsLeft || _done.apply(self, hookArgs); + } + } + function handleError (err) { + if ('function' == typeof lastArg) + return lastArg(err); + if (errorCb) return errorCb.call(self, err); + throw err; + } + return _next.apply(this, arguments); + }; + + proto[name].numAsyncPres = 0; + + return this; + }, + + pre: function (name, isAsync, fn, errorCb) { + if ('boolean' !== typeof arguments[1]) { + errorCb = fn; + fn = isAsync; + isAsync = false; + } + var proto = this.prototype || this + , pres = proto._pres = proto._pres || {}; + + this._lazySetupHooks(proto, name, errorCb); + + if (fn.isAsync = isAsync) { + proto[name].numAsyncPres++; + } + + (pres[name] = pres[name] || []).push(fn); + return this; + }, + post: function (name, isAsync, fn) { + if (arguments.length === 2) { + fn = isAsync; + isAsync = false; + } + var proto = this.prototype || this + , posts = proto._posts = proto._posts || {}; + + this._lazySetupHooks(proto, name); + (posts[name] = posts[name] || []).push(fn); + return this; + }, + removePre: function (name, fnToRemove) { + var proto = this.prototype || this + , pres = proto._pres || (proto._pres || {}); + if (!pres[name]) return this; + if (arguments.length === 1) { + // Remove all pre callbacks for hook `name` + pres[name].length = 0; + } else { + pres[name] = pres[name].filter( function (currFn) { + return currFn !== fnToRemove; + }); + } + return this; + }, + _lazySetupHooks: function (proto, methodName, errorCb) { + if ('undefined' === typeof proto[methodName].numAsyncPres) { + this.hook(methodName, proto[methodName], errorCb); + } + } +}; + +function once (fn, scope) { + return function fnWrapper () { + if (fnWrapper.hookCalled) return; + fnWrapper.hookCalled = true; + fn.apply(scope, arguments); + }; +} diff --git a/node_modules/hooks/package.json b/node_modules/hooks/package.json new file mode 100644 index 0000000..b1cf744 --- /dev/null +++ b/node_modules/hooks/package.json @@ -0,0 +1,51 @@ +{ + "name": "hooks", + "description": "Adds pre and post hook functionality to your JavaScript methods.", + "version": "0.2.0", + "keywords": [ + "node", + "hooks", + "middleware", + "pre", + "post" + ], + "homepage": "https://github.com/bnoguchi/hooks-js/", + "repository": { + "type": "git", + "url": "git://github.com/bnoguchi/hooks-js.git" + }, + "author": { + "name": "Brian Noguchi", + "email": "brian.noguchi@gmail.com", + "url": "https://github.com/bnoguchi/" + }, + "main": "./hooks.js", + "directories": { + "lib": "." + }, + "scripts": { + "test": "make test" + }, + "dependencies": {}, + "devDependencies": { + "expresso": ">=0.7.6", + "should": ">=0.2.1", + "underscore": ">=1.1.4" + }, + "engines": { + "node": ">=0.4.0" + }, + "licenses": [ + "MIT" + ], + "optionalDependencies": {}, + "_id": "hooks@0.2.0", + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "0f43df2d3610eebb1e241d8c8b61b954ad858b9e" + }, + "_from": "hooks@" +} diff --git a/node_modules/hooks/test.js b/node_modules/hooks/test.js new file mode 100644 index 0000000..d7d3822 --- /dev/null +++ b/node_modules/hooks/test.js @@ -0,0 +1,685 @@ +var hooks = require('./hooks') + , should = require('should') + , assert = require('assert') + , _ = require('underscore'); + +// TODO Add in test for making sure all pres get called if pre is defined directly on an instance. +// TODO Test for calling `done` twice or `next` twice in the same function counts only once +module.exports = { + 'should be able to assign multiple hooks at once': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook({ + hook1: function (a) {}, + hook2: function (b) {} + }); + var a = new A(); + assert.equal(typeof a.hook1, 'function'); + assert.equal(typeof a.hook2, 'function'); + }, + 'should run without pres and posts when not present': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + var a = new A(); + a.save(); + a.value.should.equal(1); + }, + 'should run with pres when present': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + A.pre('save', function (next) { + this.preValue = 2; + next(); + }); + var a = new A(); + a.save(); + a.value.should.equal(1); + a.preValue.should.equal(2); + }, + 'should run with posts when present': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + A.post('save', function (next) { + this.value = 2; + next(); + }); + var a = new A(); + a.save(); + a.value.should.equal(2); + }, + 'should run pres and posts when present': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + A.pre('save', function (next) { + this.preValue = 2; + next(); + }); + A.post('save', function (next) { + this.value = 3; + next(); + }); + var a = new A(); + a.save(); + a.value.should.equal(3); + a.preValue.should.equal(2); + }, + 'should run posts after pres': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + A.pre('save', function (next) { + this.override = 100; + next(); + }); + A.post('save', function (next) { + this.override = 200; + next(); + }); + var a = new A(); + a.save(); + a.value.should.equal(1); + a.override.should.equal(200); + }, + 'should not run a hook if a pre fails': function () { + var A = function () {}; + _.extend(A, hooks); + var counter = 0; + A.hook('save', function () { + this.value = 1; + }, function (err) { + counter++; + }); + A.pre('save', true, function (next, done) { + next(new Error()); + }); + var a = new A(); + a.save(); + counter.should.equal(1); + assert.equal(typeof a.value, 'undefined'); + }, + 'should be able to run multiple pres': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + A.pre('save', function (next) { + this.v1 = 1; + next(); + }).pre('save', function (next) { + this.v2 = 2; + next(); + }); + var a = new A(); + a.save(); + a.v1.should.equal(1); + a.v2.should.equal(2); + }, + 'should run multiple pres until a pre fails and not call the hook': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }, function (err) {}); + A.pre('save', function (next) { + this.v1 = 1; + next(); + }).pre('save', function (next) { + next(new Error()); + }).pre('save', function (next) { + this.v3 = 3; + next(); + }); + var a = new A(); + a.save(); + a.v1.should.equal(1); + assert.equal(typeof a.v3, 'undefined'); + assert.equal(typeof a.value, 'undefined'); + }, + 'should be able to run multiple posts': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + A.post('save', function (next) { + this.value = 2; + next(); + }).post('save', function (next) { + this.value = 3.14; + next(); + }).post('save', function (next) { + this.v3 = 3; + next(); + }); + var a = new A(); + a.save(); + assert.equal(a.value, 3.14); + assert.equal(a.v3, 3); + }, + 'should run only posts up until an error': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }, function (err) {}); + A.post('save', function (next) { + this.value = 2; + next(); + }).post('save', function (next) { + this.value = 3; + next(new Error()); + }).post('save', function (next) { + this.value = 4; + next(); + }); + var a = new A(); + a.save(); + a.value.should.equal(3); + }, + "should fall back first to the hook method's last argument as the error handler if it is a function of arity 1 or 2": function () { + var A = function () {}; + _.extend(A, hooks); + var counter = 0; + A.hook('save', function (callback) { + this.value = 1; + }); + A.pre('save', true, function (next, done) { + next(new Error()); + }); + var a = new A(); + a.save( function (err) { + if (err instanceof Error) counter++; + }); + counter.should.equal(1); + should.deepEqual(undefined, a.value); + }, + 'should fall back second to the default error handler if specified': function () { + var A = function () {}; + _.extend(A, hooks); + var counter = 0; + A.hook('save', function (callback) { + this.value = 1; + }, function (err) { + if (err instanceof Error) counter++; + }); + A.pre('save', true, function (next, done) { + next(new Error()); + }); + var a = new A(); + a.save(); + counter.should.equal(1); + should.deepEqual(undefined, a.value); + }, + 'fallback default error handler should scope to the object': function () { + var A = function () { + this.counter = 0; + }; + _.extend(A, hooks); + var counter = 0; + A.hook('save', function (callback) { + this.value = 1; + }, function (err) { + if (err instanceof Error) this.counter++; + }); + A.pre('save', true, function (next, done) { + next(new Error()); + }); + var a = new A(); + a.save(); + a.counter.should.equal(1); + should.deepEqual(undefined, a.value); + }, + 'should fall back last to throwing the error': function () { + var A = function () {}; + _.extend(A, hooks); + var counter = 0; + A.hook('save', function (err) { + if (err instanceof Error) return counter++; + this.value = 1; + }); + A.pre('save', true, function (next, done) { + next(new Error()); + }); + var a = new A(); + var didCatch = false; + try { + a.save(); + } catch (e) { + didCatch = true; + e.should.be.an.instanceof(Error); + counter.should.equal(0); + assert.equal(typeof a.value, 'undefined'); + } + didCatch.should.be.true; + }, + "should proceed without mutating arguments if `next(null)` is called in a serial pre, and the last argument of the target method is a callback with node-like signature function (err, obj) {...} or function (err) {...}": function () { + var A = function () {}; + _.extend(A, hooks); + var counter = 0; + A.prototype.save = function (callback) { + this.value = 1; + callback(); + }; + A.pre('save', function (next) { + next(null); + }); + var a = new A(); + a.save( function (err) { + if (err instanceof Error) counter++; + else counter--; + }); + counter.should.equal(-1); + a.value.should.eql(1); + }, + "should proceed with mutating arguments if `next(null)` is callback in a serial pre, and the last argument of the target method is not a function": function () { + var A = function () {}; + _.extend(A, hooks); + A.prototype.set = function (v) { + this.value = v; + }; + A.pre('set', function (next) { + next(null); + }); + var a = new A(); + a.set(1); + should.strictEqual(null, a.value); + }, + 'should not run any posts if a pre fails': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 2; + }, function (err) {}); + A.pre('save', function (next) { + this.value = 1; + next(new Error()); + }).post('save', function (next) { + this.value = 3; + next(); + }); + var a = new A(); + a.save(); + a.value.should.equal(1); + }, + + "can pass the hook's arguments verbatim to pres": function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('set', function (path, val) { + this[path] = val; + }); + A.pre('set', function (next, path, val) { + path.should.equal('hello'); + val.should.equal('world'); + next(); + }); + var a = new A(); + a.set('hello', 'world'); + a.hello.should.equal('world'); + }, +// "can pass the hook's arguments as an array to pres": function () { +// // Great for dynamic arity - e.g., slice(...) +// var A = function () {}; +// _.extend(A, hooks); +// A.hook('set', function (path, val) { +// this[path] = val; +// }); +// A.pre('set', function (next, hello, world) { +// hello.should.equal('hello'); +// world.should.equal('world'); +// next(); +// }); +// var a = new A(); +// a.set('hello', 'world'); +// assert.equal(a.hello, 'world'); +// }, + "can pass the hook's arguments verbatim to posts": function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('set', function (path, val) { + this[path] = val; + }); + A.post('set', function (next, path, val) { + path.should.equal('hello'); + val.should.equal('world'); + next(); + }); + var a = new A(); + a.set('hello', 'world'); + assert.equal(a.hello, 'world'); + }, +// "can pass the hook's arguments as an array to posts": function () { +// var A = function () {}; +// _.extend(A, hooks); +// A.hook('set', function (path, val) { +// this[path] = val; +// }); +// A.post('set', function (next, halt, args) { +// assert.equal(args[0], 'hello'); +// assert.equal(args[1], 'world'); +// next(); +// }); +// var a = new A(); +// a.set('hello', 'world'); +// assert.equal(a.hello, 'world'); +// }, + "pres should be able to modify and pass on a modified version of the hook's arguments": function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('set', function (path, val) { + this[path] = val; + assert.equal(arguments[2], 'optional'); + }); + A.pre('set', function (next, path, val) { + next('foo', 'bar'); + }); + A.pre('set', function (next, path, val) { + assert.equal(path, 'foo'); + assert.equal(val, 'bar'); + next('rock', 'says', 'optional'); + }); + A.pre('set', function (next, path, val, opt) { + assert.equal(path, 'rock'); + assert.equal(val, 'says'); + assert.equal(opt, 'optional'); + next(); + }); + var a = new A(); + a.set('hello', 'world'); + assert.equal(typeof a.hello, 'undefined'); + a.rock.should.equal('says'); + }, + 'posts should see the modified version of arguments if the pres modified them': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('set', function (path, val) { + this[path] = val; + }); + A.pre('set', function (next, path, val) { + next('foo', 'bar'); + }); + A.post('set', function (next, path, val) { + path.should.equal('foo'); + val.should.equal('bar'); + }); + var a = new A(); + a.set('hello', 'world'); + assert.equal(typeof a.hello, 'undefined'); + a.foo.should.equal('bar'); + }, + 'should pad missing arguments (relative to expected arguments of the hook) with null': function () { + // Otherwise, with hookFn = function (a, b, next, ), + // if we use hookFn(a), then because the pre functions are of the form + // preFn = function (a, b, next, ), then it actually gets executed with + // preFn(a, next, ), so when we call next() from within preFn, we are actually + // calling () + + var A = function () {}; + _.extend(A, hooks); + A.hook('set', function (path, val, opts) { + this[path] = val; + }); + A.pre('set', function (next, path, val, opts) { + next('foo', 'bar'); + assert.equal(typeof opts, 'undefined'); + }); + var a = new A(); + a.set('hello', 'world'); + }, + + 'should not invoke the target method until all asynchronous middleware have invoked dones': function () { + var counter = 0; + var A = function () {}; + _.extend(A, hooks); + A.hook('set', function (path, val) { + counter++; + this[path] = val; + counter.should.equal(7); + }); + A.pre('set', function (next, path, val) { + counter++; + next(); + }); + A.pre('set', true, function (next, done, path, val) { + counter++; + setTimeout(function () { + counter++; + done(); + }, 1000); + next(); + }); + A.pre('set', function (next, path, val) { + counter++; + next(); + }); + A.pre('set', true, function (next, done, path, val) { + counter++; + setTimeout(function () { + counter++; + done(); + }, 500); + next(); + }); + var a = new A(); + a.set('hello', 'world'); + }, + + 'invoking a method twice should run its async middleware twice': function () { + var counter = 0; + var A = function () {}; + _.extend(A, hooks); + A.hook('set', function (path, val) { + this[path] = val; + if (path === 'hello') counter.should.equal(1); + if (path === 'foo') counter.should.equal(2); + }); + A.pre('set', true, function (next, done, path, val) { + setTimeout(function () { + counter++; + done(); + }, 1000); + next(); + }); + var a = new A(); + a.set('hello', 'world'); + a.set('foo', 'bar'); + }, + + 'calling the same done multiple times should have the effect of only calling it once': function () { + var A = function () { + this.acked = false; + }; + _.extend(A, hooks); + A.hook('ack', function () { + console.log("UH OH, YOU SHOULD NOT BE SEEING THIS"); + this.acked = true; + }); + A.pre('ack', true, function (next, done) { + next(); + done(); + done(); + }); + A.pre('ack', true, function (next, done) { + next(); + // Notice that done() is not invoked here + }); + var a = new A(); + a.ack(); + setTimeout( function () { + a.acked.should.be.false; + }, 1000); + }, + + 'calling the same next multiple times should have the effect of only calling it once': function (beforeExit) { + var A = function () { + this.acked = false; + }; + _.extend(A, hooks); + A.hook('ack', function () { + console.log("UH OH, YOU SHOULD NOT BE SEEING THIS"); + this.acked = true; + }); + A.pre('ack', function (next) { + // force a throw to re-exec next() + try { + next(new Error('bam')); + } catch (err) { + next(); + } + }); + A.pre('ack', function (next) { + next(); + }); + var a = new A(); + a.ack(); + beforeExit( function () { + a.acked.should.be.false; + }); + }, + + 'asynchronous middleware should be able to pass an error via `done`, stopping the middleware chain': function () { + var counter = 0; + var A = function () {}; + _.extend(A, hooks); + A.hook('set', function (path, val, fn) { + counter++; + this[path] = val; + fn(null); + }); + A.pre('set', true, function (next, done, path, val, fn) { + setTimeout(function () { + counter++; + done(new Error); + }, 1000); + next(); + }); + var a = new A(); + a.set('hello', 'world', function (err) { + err.should.be.an.instanceof(Error); + should.strictEqual(undefined, a['hello']); + counter.should.eql(1); + }); + }, + + 'should be able to remove a particular pre': function () { + var A = function () {} + , preTwo; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + A.pre('save', function (next) { + this.preValueOne = 2; + next(); + }); + A.pre('save', preTwo = function (next) { + this.preValueTwo = 4; + next(); + }); + A.removePre('save', preTwo); + var a = new A(); + a.save(); + a.value.should.equal(1); + a.preValueOne.should.equal(2); + should.strictEqual(undefined, a.preValueTwo); + }, + + 'should be able to remove all pres associated with a hook': function () { + var A = function () {}; + _.extend(A, hooks); + A.hook('save', function () { + this.value = 1; + }); + A.pre('save', function (next) { + this.preValueOne = 2; + next(); + }); + A.pre('save', function (next) { + this.preValueTwo = 4; + next(); + }); + A.removePre('save'); + var a = new A(); + a.save(); + a.value.should.equal(1); + should.strictEqual(undefined, a.preValueOne); + should.strictEqual(undefined, a.preValueTwo); + }, + + '#pre should lazily make a method hookable': function () { + var A = function () {}; + _.extend(A, hooks); + A.prototype.save = function () { + this.value = 1; + }; + A.pre('save', function (next) { + this.preValue = 2; + next(); + }); + var a = new A(); + a.save(); + a.value.should.equal(1); + a.preValue.should.equal(2); + }, + + '#pre lazily making a method hookable should be able to provide a default errorHandler as the last argument': function () { + var A = function () {}; + var preValue = ""; + _.extend(A, hooks); + A.prototype.save = function () { + this.value = 1; + }; + A.pre('save', function (next) { + next(new Error); + }, function (err) { + preValue = 'ERROR'; + }); + var a = new A(); + a.save(); + should.strictEqual(undefined, a.value); + preValue.should.equal('ERROR'); + }, + + '#post should lazily make a method hookable': function () { + var A = function () {}; + _.extend(A, hooks); + A.prototype.save = function () { + this.value = 1; + }; + A.post('save', function (next) { + this.value = 2; + next(); + }); + var a = new A(); + a.save(); + a.value.should.equal(2); + }, + + "a lazy hooks setup should handle errors via a method's last argument, if it's a callback": function () { + var A = function () {}; + _.extend(A, hooks); + A.prototype.save = function (fn) {}; + A.pre('save', function (next) { + next(new Error("hi there")); + }); + var a = new A(); + a.save( function (err) { + err.should.be.an.instanceof(Error); + }); + } +}; diff --git a/node_modules/mocha/.npmignore b/node_modules/mocha/.npmignore new file mode 100644 index 0000000..f4f8678 --- /dev/null +++ b/node_modules/mocha/.npmignore @@ -0,0 +1,5 @@ +support +test +examples +*.sock +lib-cov diff --git a/node_modules/mocha/.travis.yml b/node_modules/mocha/.travis.yml new file mode 100644 index 0000000..6a79c0c --- /dev/null +++ b/node_modules/mocha/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - 0.4 + - 0.6 + #- 0.7 \ No newline at end of file diff --git a/node_modules/mocha/History.md b/node_modules/mocha/History.md new file mode 100644 index 0000000..4993f3f --- /dev/null +++ b/node_modules/mocha/History.md @@ -0,0 +1,317 @@ + +1.0.1 / 2012-04-04 +================== + + * Fixed `.timeout()` in hooks + * Fixed: allow callback for `mocha.run()` in client version + * Fixed browser hook error display. Closes #361 + +1.0.0 / 2012-03-24 +================== + + * Added js API. Closes #265 + * Added: initial run of tests with `--watch`. Closes #345 + * Added: mark `location` as a global on the CS. Closes #311 + * Added `markdown` reporter (github flavour) + * Added: scrolling menu to coverage.html. Closes #335 + * Added source line to html report for Safari [Tyson Tate] + * Added "min" reporter, useful for `--watch` [Jakub Nešetřil] + * Added support for arbitrary compilers via . Closes #338 [Ian Young] + * Added Teamcity export to lib/reporters/index [Michael Riley] + * Fixed chopping of first char in error reporting. Closes #334 [reported by topfunky] + * Fixed terrible FF / Opera stack traces + +0.14.1 / 2012-03-06 +================== + + * Added lib-cov to _.npmignore_ + * Added reporter to `mocha.run([reporter])` as argument + * Added some margin-top to the HTML reporter + * Removed jQuery dependency + * Fixed `--watch`: purge require cache. Closes #266 + +0.14.0 / 2012-03-01 +================== + + * Added string diff support for terminal reporters + +0.13.0 / 2012-02-23 +================== + + * Added preliminary test coverage support. Closes #5 + * Added `HTMLCov` reporter + * Added `JSONCov` reporter [kunklejr] + * Added `xdescribe()` and `xit()` to the BDD interface. Closes #263 (docs * Changed: make json reporter output pretty json + * Fixed node-inspector support, swapped `--debug` for `debug` to match node. +needed) +Closes #247 + +0.12.1 / 2012-02-14 +================== + + * Added `npm docs mocha` support [TooTallNate] + * Added a `Context` object used for hook and test-case this. Closes #253 + * Fixed `Suite#clone()` `.ctx` reference. Closes #262 + +0.12.0 / 2012-02-02 +================== + + * Added .coffee `--watch` support. Closes #242 + * Added support to `--require` files relative to the CWD. Closes #241 + * Added quick n dirty syntax highlighting. Closes #248 + * Changed: made HTML progress indicator smaller + * Fixed xunit errors attribute [dhendo] + +0.10.2 / 2012-01-21 +================== + + * Fixed suite count in reporter stats. Closes #222 + * Fixed `done()` after timeout error reporting [Phil Sung] + * Changed the 0-based errors to 1 + +0.10.1 / 2012-01-17 +================== + + * Added support for node 0.7.x + * Fixed absolute path support. Closes #215 [kompiro] + * Fixed `--no-colors` option [Jussi Virtanen] + * Fixed Arial CSS typo in the correct file + +0.10.0 / 2012-01-13 +================== + + * Added `-b, --bail` to exit on first exception [guillermo] + * Added support for `-gc` / `--expose-gc` [TooTallNate] + * Added `qunit`-inspired interface + * Added MIT LICENSE. Closes #194 + * Added: `--watch` all .js in the CWD. Closes #139 + * Fixed `self.test` reference in runner. Closes #189 + * Fixed double reporting of uncaught exceptions after timeout. Closes #195 + +0.8.2 / 2012-01-05 +================== + + * Added test-case context support. Closes #113 + * Fixed exit status. Closes #187 + * Update commander. Closes #190 + +0.8.1 / 2011-12-30 +================== + + * Fixed reporting of uncaught exceptions. Closes #183 + * Fixed error message defaulting [indutny] + * Changed mocha(1) from bash to node for windows [Nathan Rajlich] + +0.8.0 / 2011-12-28 +================== + + * Added `XUnit` reporter [FeeFighters/visionmedia] + * Added `say(1)` notification support [Maciej Małecki] + * Changed: fail when done() is invoked with a non-Error. Closes #171 + * Fixed `err.stack`, defaulting to message. Closes #180 + * Fixed: `make tm` mkdir -p the dest. Closes #137 + * Fixed mocha(1) --help bin name + * Fixed `-d` for `--debug` support + +0.7.1 / 2011-12-22 +================== + + * Removed `mocha-debug(1)`, use `mocha --debug` + * Fixed CWD relative requires + * Fixed growl issue on windows [Raynos] + * Fixed: platform specific line endings [TooTallNate] + * Fixed: escape strings in HTML reporter. Closes #164 + +0.7.0 / 2011-12-18 +================== + + * Added support for IE{7,8} [guille] + * Changed: better browser nextTick implementation [guille] + +0.6.0 / 2011-12-18 +================== + + * Added setZeroTimeout timeout for browser (nicer stack traces). Closes #153 + * Added "view source" on hover for HTML reporter to make it obvious + * Changed: replace custom growl with growl lib + * Fixed duplicate reporting for HTML reporter. Closes #154 + * Fixed silent hook errors in the HTML reporter. Closes #150 + +0.5.0 / 2011-12-14 +================== + + * Added: push node_modules directory onto module.paths for relative require Closes #93 + * Added teamcity reporter [blindsey] + * Fixed: recover from uncaught exceptions for tests. Closes #94 + * Fixed: only emit "test end" for uncaught within test, not hook + +0.4.0 / 2011-12-14 +================== + + * Added support for test-specific timeouts via `this.timeout(0)`. Closes #134 + * Added guillermo's client-side EventEmitter. Closes #132 + * Added progress indicator to the HTML reporter + * Fixed slow browser tests. Closes #135 + * Fixed "suite" color for light terminals + * Fixed `require()` leak spotted by [guillermo] + +0.3.6 / 2011-12-09 +================== + + * Removed suite merging (for now) + +0.3.5 / 2011-12-08 +================== + + * Added support for `window.onerror` [guillermo] + * Fixed: clear timeout on uncaught exceptions. Closes #131 [guillermo] + * Added `mocha.css` to PHONY list. + * Added `mocha.js` to PHONY list. + +0.3.4 / 2011-12-08 +================== + + * Added: allow `done()` to be called with non-Error + * Added: return Runner from `mocha.run()`. Closes #126 + * Fixed: run afterEach even on failures. Closes #125 + * Fixed clobbering of current runnable. Closes #121 + +0.3.3 / 2011-12-08 +================== + + * Fixed hook timeouts. Closes #120 + * Fixed uncaught exceptions in hooks + +0.3.2 / 2011-12-05 +================== + + * Fixed weird reporting when `err.message` is not present + +0.3.1 / 2011-12-04 +================== + + * Fixed hook event emitter leak. Closes #117 + * Fixed: export `Spec` constructor. Closes #116 + +0.3.0 / 2011-12-04 +================== + + * Added `-w, --watch`. Closes #72 + * Added `--ignore-leaks` to ignore global leak checking + * Added browser `?grep=pattern` support + * Added `--globals ` to specify accepted globals. Closes #99 + * Fixed `mocha-debug(1)` on some systems. Closes #232 + * Fixed growl total, use `runner.total` + +0.2.0 / 2011-11-30 +================== + + * Added `--globals ` to specify accepted globals. Closes #99 + * Fixed funky highlighting of messages. Closes #97 + * Fixed `mocha-debug(1)`. Closes #232 + * Fixed growl total, use runner.total + +0.1.0 / 2011-11-29 +================== + + * Added `suiteSetup` and `suiteTeardown` to TDD interface [David Henderson] + * Added growl icons. Closes #84 + * Fixed coffee-script support + +0.0.8 / 2011-11-25 +================== + + * Fixed: use `Runner#total` for accurate reporting + +0.0.7 / 2011-11-25 +================== + + * Added `Hook` + * Added `Runnable` + * Changed: `Test` is `Runnable` + * Fixed global leak reporting in hooks + * Fixed: > 2 calls to done() only report the error once + * Fixed: clear timer on failure. Closes #80 + +0.0.6 / 2011-11-25 +================== + + * Fixed return on immediate async error. Closes #80 + +0.0.5 / 2011-11-24 +================== + + * Fixed: make mocha.opts whitespace less picky [kkaefer] + +0.0.4 / 2011-11-24 +================== + + * Added `--interfaces` + * Added `--reporters` + * Added `-c, --colors`. Closes #69 + * Fixed hook timeouts + +0.0.3 / 2011-11-23 +================== + + * Added `-C, --no-colors` to explicitly disable + * Added coffee-script support + +0.0.2 / 2011-11-22 +================== + + * Fixed global leak detection due to Safari bind() change + * Fixed: escape html entities in Doc reporter + * Fixed: escape html entities in HTML reporter + * Fixed pending test support for HTML reporter. Closes #66 + +0.0.1 / 2011-11-22 +================== + + * Added `--timeout` second shorthand support, ex `--timeout 3s`. + * Fixed "test end" event for uncaughtExceptions. Closes #61 + +0.0.1-alpha6 / 2011-11-19 +================== + + * Added travis CI support (needs enabling when public) + * Added preliminary browser support + * Added `make mocha.css` target. Closes #45 + * Added stack trace to TAP errors. Closes #52 + * Renamed tearDown to teardown. Closes #49 + * Fixed: cascading hooksc. Closes #30 + * Fixed some colors for non-tty + * Fixed errors thrown in sync test-cases due to nextTick + * Fixed Base.window.width... again give precedence to 0.6.x + +0.0.1-alpha5 / 2011-11-17 +================== + + * Added `doc` reporter. Closes #33 + * Added suite merging. Closes #28 + * Added TextMate bundle and `make tm`. Closes #20 + +0.0.1-alpha4 / 2011-11-15 +================== + + * Fixed getWindowSize() for 0.4.x + +0.0.1-alpha3 / 2011-11-15 +================== + + * Added `-s, --slow ` to specify "slow" test threshold + * Added `mocha-debug(1)` + * Added `mocha.opts` support. Closes #31 + * Added: default [files] to _test/*.js_ + * Added protection against multiple calls to `done()`. Closes #35 + * Changed: bright yellow for slow Dot reporter tests + +0.0.1-alpha1 / 2011-11-08 +================== + + * Missed this one :) + +0.0.1-alpha1 / 2011-11-08 +================== + + * Initial release diff --git a/node_modules/mocha/LICENSE b/node_modules/mocha/LICENSE new file mode 100644 index 0000000..b66fae6 --- /dev/null +++ b/node_modules/mocha/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 20011-2012 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/mocha/Makefile b/node_modules/mocha/Makefile new file mode 100644 index 0000000..91a1da7 --- /dev/null +++ b/node_modules/mocha/Makefile @@ -0,0 +1,118 @@ + +REPORTER = dot +TM_DEST = ~/Library/Application\ Support/TextMate/Bundles +TM_BUNDLE = JavaScript\ mocha.tmbundle +SRC = $(shell find lib -name "*.js" -type f) +SUPPORT = $(wildcard support/*.js) + +all: mocha.js mocha.css + +mocha.css: test/browser/style.css + cp -f $< $@ + +mocha.js: $(SRC) $(SUPPORT) + @node support/compile $(SRC) + @cat \ + support/head.js \ + _mocha.js \ + support/{tail,foot}.js \ + > mocha.js + +clean: + rm -f mocha.{js,css} + rm -fr lib-cov + rm -f coverage.html + +test-cov: lib-cov + @COV=1 $(MAKE) test REPORTER=html-cov > coverage.html + +lib-cov: + @rm -fr ./$@ + @jscoverage lib $@ + +test: test-unit + +test-all: test-bdd test-tdd test-qunit test-exports test-unit test-grep test-jsapi test-compilers + +test-jsapi: + @node test/jsapi + +test-unit: + @./bin/mocha \ + --reporter $(REPORTER) \ + test/acceptance/*.js \ + test/*.js + +test-compilers: + @./bin/mocha \ + --reporter $(REPORTER) \ + --compilers coffee:coffee-script,foo:./test/compiler/foo \ + test/acceptance/test.coffee \ + test/acceptance/test.foo + +test-bdd: + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui bdd \ + test/acceptance/interfaces/bdd + +test-tdd: + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui tdd \ + test/acceptance/interfaces/tdd + +test-qunit: + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui qunit \ + test/acceptance/interfaces/qunit + +test-exports: + @./bin/mocha \ + --reporter $(REPORTER) \ + --ui exports \ + test/acceptance/interfaces/exports + +test-grep: + @./bin/mocha \ + --reporter $(REPORTER) \ + --grep fast \ + test/acceptance/misc/grep + +test-bail: + @./bin/mocha \ + --reporter $(REPORTER) \ + --bail \ + test/acceptance/misc/bail + +non-tty: + @./bin/mocha \ + --reporter dot \ + test/acceptance/interfaces/bdd 2>&1 > /tmp/dot.out + + @echo dot: + @cat /tmp/dot.out + + @./bin/mocha \ + --reporter list \ + test/acceptance/interfaces/bdd 2>&1 > /tmp/list.out + + @echo list: + @cat /tmp/list.out + + @./bin/mocha \ + --reporter spec \ + test/acceptance/interfaces/bdd 2>&1 > /tmp/spec.out + + @echo spec: + @cat /tmp/spec.out + +watch: + @watch -q $(MAKE) mocha.{js,css} + +tm: + mkdir -p $(TM_DEST)/$(TM_BUNDLE) + cp -fr editors/$(TM_BUNDLE) $(TM_DEST)/$(TM_BUNDLE) + +.PHONY: test-cov test-jsapi test-compilers watch test test-all test-bdd test-tdd test-qunit test-exports test-unit non-tty test-grep tm clean diff --git a/node_modules/mocha/Readme.md b/node_modules/mocha/Readme.md new file mode 100644 index 0000000..d534371 --- /dev/null +++ b/node_modules/mocha/Readme.md @@ -0,0 +1,34 @@ + [![Build Status](https://secure.travis-ci.org/visionmedia/mocha.png)](http://travis-ci.org/visionmedia/mocha) + + [![Mocha test framework](http://f.cl.ly/items/3l1k0n2A1U3M1I1L210p/Screen%20Shot%202012-02-24%20at%202.21.43%20PM.png)](http://visionmedia.github.com/mocha) + + Mocha is a simple, flexible, fun JavaScript test framework for node.js and the browser. For more information view the [documentation](http://visionmedia.github.com/mocha). + +## Contributors + +``` +project: mocha +commits: 502 +files : 86 +authors: + 352 Tj Holowaychuk 70.1% + 98 TJ Holowaychuk 19.5% + 21 Guillermo Rauch 4.2% + 6 James Carr 1.2% + 4 Joshua Krall 0.8% + 3 Ben Lindsey 0.6% + 3 Nathan Rajlich 0.6% + 2 FARKAS Máté 0.4% + 2 Quang Van 0.4% + 1 Steve Mason 0.2% + 1 Yuest Wang 0.2% + 1 hokaccha 0.2% + 1 David Henderson 0.2% + 1 Fedor Indutny 0.2% + 1 Fredrik Lindin 0.2% + 1 Harry Brundage 0.2% + 1 Konstantin Käfer 0.2% + 1 Maciej Małecki 0.2% + 1 Raynos 0.2% + 1 Ryunosuke SATO 0.2% +``` diff --git a/node_modules/mocha/_mocha.js b/node_modules/mocha/_mocha.js new file mode 100644 index 0000000..c9bd1c8 --- /dev/null +++ b/node_modules/mocha/_mocha.js @@ -0,0 +1,3916 @@ + +// 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.charAt(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("browser/debug.js", function(module, exports, require){ + +module.exports = function(type){ + return function(){ + + } +}; +}); // module: browser/debug.js + +require.register("browser/diff.js", function(module, exports, require){ + +}); // module: browser/diff.js + +require.register("browser/events.js", function(module, exports, require){ + +/** + * Module exports. + */ + +exports.EventEmitter = EventEmitter; + +/** + * Check if `obj` is an array. + */ + +function isArray(obj) { + return '[object Array]' == {}.toString.call(obj); +} + +/** + * 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 (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 (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) { + 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 (!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 = [].slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (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; +}; +}); // module: browser/events.js + +require.register("browser/fs.js", function(module, exports, require){ + +}); // module: browser/fs.js + +require.register("browser/path.js", function(module, exports, require){ + +}); // module: browser/path.js + +require.register("browser/progress.js", function(module, exports, require){ + +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ + +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.size = function(n){ + this._size = n; + return this; +}; + +/** + * Set text to `str`. + * + * @param {String} str + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.text = function(str){ + this._text = str; + return this; +}; + +/** + * Set font size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.fontSize = function(n){ + this._fontSize = n; + return this; +}; + +/** + * Set font `family`. + * + * @param {String} family + * @return {Progress} for chaining + */ + +Progress.prototype.font = function(family){ + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + */ + +Progress.prototype.update = function(n){ + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} for chaining + */ + +Progress.prototype.draw = function(ctx){ + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + + return this; +}; + +}); // module: browser/progress.js + +require.register("browser/tty.js", function(module, exports, require){ + +exports.isatty = function(){ + return true; +}; + +exports.getWindowSize = function(){ + return [window.innerHeight, window.innerWidth]; +}; +}); // module: browser/tty.js + +require.register("context.js", function(module, exports, require){ + +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @api private + */ + +function Context(){} + +/** + * Set the context `Runnable` to `runnable`. + * + * @param {Runnable} runnable + * @return {Context} + * @api private + */ + +Context.prototype.runnable = function(runnable){ + this._runnable = runnable; + return this; +}; + +/** + * Set test timeout `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.timeout = function(ms){ + this._runnable.timeout(ms); + return this; +}; + +/** + * Inspect the context void of `._runnable`. + * + * @return {String} + * @api private + */ + +Context.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + return '_runnable' == key + ? undefined + : val; + }, 2); +}; + +}); // module: context.js + +require.register("hook.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Hook.prototype = new Runnable; +Hook.prototype.constructor = Hook; + + +}); // module: hook.js + +require.register("interfaces/bdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * BDD-style interface: + * + * describe('Array', function(){ + * describe('#indexOf()', function(){ + * it('should return -1 when not present', function(){ + * + * }); + * + * it('should return the index when present', function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + // noop variants + + context.xdescribe = function(){}; + context.xit = function(){}; + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn(); + suites.shift(); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; + +}); // module: interfaces/bdd.js + +require.register("interfaces/exports.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function(){ + * + * }, + * + * 'should return the correct index when the value is present': function(){ + * + * } + * } + * }; + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('require', visit); + + function visit(obj) { + var suite; + for (var key in obj) { + if ('function' == typeof obj[key]) { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + suites[0].addTest(new Test(key, fn)); + } + } else { + var suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key]); + suites.shift(); + } + } + } +}; +}); // module: interfaces/exports.js + +require.register("interfaces/index.js", function(module, exports, require){ + +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); + +}); // module: interfaces/index.js + +require.register("interfaces/qunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function(){ + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function(){ + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function(){ + * ok('foo'.length == 3); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; + +}); // module: interfaces/qunit.js + +require.register("interfaces/tdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * suite('Array', function(){ + * suite('#indexOf()', function(){ + * suiteSetup(function(){ + * + * }); + * + * test('should return -1 when not present', function(){ + * + * }); + * + * test('should return the index when present', function(){ + * + * }); + * + * suiteTeardown(function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + /** + * Execute before each test case. + */ + + context.setup = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.teardown = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Execute before the suite. + */ + + context.suiteSetup = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after the suite. + */ + + context.suiteTeardown = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.suite = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn(); + suites.shift(); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; + +}); // module: interfaces/tdd.js + +require.register("mocha.js", function(module, exports, require){ + +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('browser/path'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * Library version. + */ + +exports.version = '1.0.1'; + +/** + * Expose internals. + */ + +exports.utils = require('./utils'); +exports.interfaces = require('./interfaces'); +exports.reporters = require('./reporters'); +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @param {String} name + * @return {String} + * @api private + */ + +function image(name) { + return __dirname + '/../images/' + name + '.png'; +} + +/** + * Setup mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `ignoreLeaks` ignore global leaks + * + * @param {Object} options + * @api public + */ + +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + this.suite = new exports.Suite('', new exports.Context); + this.ui(options.ui); + this.reporter(options.reporter); + if (options.timeout) this.suite.timeout(options.timeout); +} + +/** + * Add test `file`. + * + * @param {String} file + * @api public + */ + +Mocha.prototype.addFile = function(file){ + this.files.push(file); + return this; +}; + +/** + * Set reporter to `name`, defaults to "dot". + * + * @param {String} name + * @api public + */ + +Mocha.prototype.reporter = function(name){ + name = name || 'dot'; + this._reporter = require('./reporters/' + name); + if (!this._reporter) throw new Error('invalid reporter "' + name + '"'); + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @param {String} bdd + * @api public + */ + +Mocha.prototype.ui = function(name){ + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) throw new Error('invalid interface "' + name + '"'); + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ + +Mocha.prototype.loadFiles = function(){ + var suite = this.suite; + this.files.forEach(function(file){ + file = path.resolve(file); + suite.emit('pre-require', global, file); + suite.emit('require', require(file), file); + suite.emit('post-require', global, file); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ + +Mocha.prototype.growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { title: 'Failed', image: image('fail') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + title: 'Passed' + , image: image('pass') + }); + } + }); +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @param {Function} fn + * @return {Runner} + * @api public + */ + +Mocha.prototype.run = function(fn){ + this.loadFiles(); + var suite = this.suite; + var options = this.options; + var runner = new exports.Runner(suite); + var reporter = new this._reporter(runner); + runner.ignoreLeaks = options.ignoreLeaks; + if (options.grep) runner.grep(options.grep); + if (options.globals) runner.globals(options.globals); + if (options.growl) this.growl(runner, reporter); + return runner.run(fn); +}; + +}); // module: mocha.js + +require.register("reporters/base.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var tty = require('browser/tty') + , diff = require('browser/diff'); + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Enable coloring by default. + */ + +exports.useColors = isatty; + +/** + * Default color map. + */ + +exports.colors = { + 'pass': 90 + , 'fail': 31 + , 'bright pass': 92 + , 'bright fail': 91 + , 'bright yellow': 93 + , 'pending': 36 + , 'suite': 0 + , 'error title': 0 + , 'error message': 31 + , 'error stack': 90 + , 'checkmark': 32 + , 'fast': 90 + , 'medium': 33 + , 'slow': 31 + , 'green': 32 + , 'light': 90 + , 'diff gutter': 90 + , 'diff added': 42 + , 'diff removed': 41 +}; + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {String} type + * @param {String} str + * @return {String} + * @api private + */ + +var color = exports.color = function(type, str) { + if (!exports.useColors) return str; + return '\033[' + exports.colors[type] + 'm' + str + '\033[0m'; +}; + +/** + * Expose term window size, with some + * defaults for when stderr is not a tty. + */ + +exports.window = { + width: isatty + ? process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1] + : 75 +}; + +/** + * Expose some basic cursor interactions + * that are common among reporters. + */ + +exports.cursor = { + hide: function(){ + process.stdout.write('\033[?25l'); + }, + + show: function(){ + process.stdout.write('\033[?25h'); + }, + + deleteLine: function(){ + process.stdout.write('\033[2K'); + }, + + beginningOfLine: function(){ + process.stdout.write('\033[0G'); + }, + + CR: function(){ + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } +}; + +/** + * A test is considered slow if it + * exceeds the following value in milliseconds. + */ + +exports.slow = 75; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures){ + console.error(); + failures.forEach(function(test, i){ + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var err = test.err + , message = err.message || '' + , stack = err.stack || message + , index = stack.indexOf(message) + message.length + , msg = stack.slice(0, index) + , actual = err.actual + , expected = err.expected; + + // actual / expected diff + if ('string' == typeof actual && 'string' == typeof expected) { + var len = Math.max(actual.length, expected.length); + + if (len < 20) msg = errorDiff(err, 'Chars'); + else msg = errorDiff(err, 'Words'); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + + fmt = color('error title', ' %s) %s:\n%s') + + color('error stack', '\n%s\n'); + } + + // indent stack trace without msg + stack = stack.slice(index ? index + 1 : index) + .replace(/^/gm, ' '); + + console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var self = this + , stats = this.stats = { suites: 0, tests: 0, passes: 0, failures: 0 } + , failures = this.failures = []; + + if (!runner) return; + this.runner = runner; + + runner.on('start', function(){ + stats.start = new Date; + }); + + runner.on('suite', function(suite){ + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function(test){ + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test){ + stats.passes = stats.passes || 0; + + var medium = exports.slow / 2; + test.speed = test.duration > exports.slow + ? 'slow' + : test.duration > medium + ? 'medium' + : 'fast'; + + stats.passes++; + }); + + runner.on('fail', function(test, err){ + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function(){ + stats.end = new Date; + stats.duration = new Date - stats.start; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ + +Base.prototype.epilogue = function(){ + var stats = this.stats + , fmt; + + console.log(); + + // failure + if (stats.failures) { + fmt = color('bright fail', ' ✖') + + color('fail', ' %d of %d tests failed') + + color('light', ':') + + console.error(fmt, stats.failures, this.runner.total); + Base.list(this.failures); + console.error(); + return; + } + + // pass + fmt = color('bright pass', ' ✔') + + color('green', ' %d tests complete') + + color('light', ' (%dms)'); + + console.log(fmt, stats.tests || 0, stats.duration); + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @param {String} str + * @param {String} len + * @return {String} + * @api private + */ + +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Return a character diff for `err`. + * + * @param {Error} err + * @return {String} + * @api private + */ + +function errorDiff(err, type) { + return diff['diff' + type](err.actual, err.expected).map(function(str){ + if (str.added) return colorLines('diff added', str.value); + if (str.removed) return colorLines('diff removed', str.value); + return str.value; + }).join(''); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @param {String} name + * @param {String} str + * @return {String} + * @api private + */ + +function colorLines(name, str) { + return str.split('\n').map(function(str){ + return color(name, str); + }).join('\n'); +} + +}); // module: reporters/base.js + +require.register("reporters/doc.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Doc(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite){ + if (suite.root) return; + ++indents; + console.log('%s
', indent()); + ++indents; + console.log('%s

%s

', indent(), suite.title); + console.log('%s
', indent()); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + console.log('%s
', indent()); + --indents; + console.log('%s
', indent()); + --indents; + }); + + runner.on('pass', function(test){ + console.log('%s
%s
', indent(), test.title); + var code = utils.escape(clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + }); +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return str; +} +}); // module: reporters/doc.js + +require.register("reporters/dot.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Dot(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , n = 0; + + runner.on('start', function(){ + process.stdout.write('\n '); + }); + + runner.on('pending', function(test){ + process.stdout.write(color('pending', '.')); + }); + + runner.on('pass', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + if ('slow' == test.speed) { + process.stdout.write(color('bright yellow', '.')); + } else { + process.stdout.write(color(test.speed, '.')); + } + }); + + runner.on('fail', function(test, err){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('fail', '.')); + }); + + runner.on('end', function(){ + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Dot.prototype = new Base; +Dot.prototype.constructor = Dot; + +}); // module: reporters/dot.js + +require.register("reporters/html-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov') + , fs = require('browser/fs'); + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTMLCov(runner) { + var jade = require('jade') + , file = __dirname + '/templates/coverage.jade' + , str = fs.readFileSync(file, 'utf8') + , fn = jade.compile(str, { filename: file }) + , self = this; + + JSONCov.call(this, runner, false); + + runner.on('end', function(){ + process.stdout.write(fn({ + cov: self.cov + , coverageClass: coverageClass + })); + }); +} + +function coverageClass(n) { + if (n >= 75) return 'high'; + if (n >= 50) return 'medium'; + if (n >= 25) return 'low'; + return 'terrible'; +} +}); // module: reporters/html-cov.js + +require.register("reporters/html.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , Progress = require('../browser/progress') + , escape = utils.escape; + +/** + * Expose `Doc`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = '
    ' + + '
  • ' + + '
  • passes: 0
  • ' + + '
  • failures: 0
  • ' + + '
  • duration: 0s
  • ' + + '
'; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTML(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , root = document.getElementById('mocha') + , stat = fragment(statsTemplate) + , items = stat.getElementsByTagName('li') + , passes = items[1].getElementsByTagName('em')[0] + , failures = items[2].getElementsByTagName('em')[0] + , duration = items[3].getElementsByTagName('em')[0] + , canvas = stat.getElementsByTagName('canvas')[0] + , stack = [root] + , progress + , ctx + + if (canvas.getContext) { + ctx = canvas.getContext('2d'); + progress = new Progress; + } + + if (!root) return error('#mocha div missing, add it to your document'); + + root.appendChild(stat); + + if (progress) progress.size(40); + + runner.on('suite', function(suite){ + if (suite.root) return; + + // suite + var el = fragment('

%s

', suite.title); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('div')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + stack.shift(); + }); + + runner.on('fail', function(test, err){ + if ('hook' == test.type || err.uncaught) runner.emit('test end', test); + }); + + runner.on('test end', function(test){ + // TODO: add to stats + var percent = stats.tests / total * 100 | 0; + if (progress) progress.update(percent).draw(ctx); + + // update stats + var ms = new Date - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + + // test + if ('passed' == test.state) { + var el = fragment('

%e

', test.title); + } else if (test.pending) { + var el = fragment('

%e

', test.title); + } else { + var el = fragment('

%e

', test.title); + var str = test.err.stack || test.err.toString(); + + // FF / Opera do not add the message + if (!~str.indexOf(test.err.message)) { + str = test.err.message + '\n' + str; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == str) str = test.err.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { + str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; + } + + el.appendChild(fragment('
%e
', str)); + } + + // toggle code + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function(){ + pre.style.display = 'none' == pre.style.display + ? 'block' + : 'none'; + }); + + // code + // TODO: defer + if (!test.pending) { + var pre = fragment('
%e
', clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + stack[0].appendChild(el); + }); +} + +/** + * Display error `msg`. + */ + +function error(msg) { + document.body.appendChild(fragment('
%s
', msg)); +} + +/** + * Return a DOM fragment from `html`. + */ + +function fragment(html) { + var args = arguments + , div = document.createElement('div') + , i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type){ + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + } + }); + + return div.firstChild; +} + +/** + * Set `el` text to `str`. + */ + +function text(el, str) { + if (el.textContent) { + el.textContent = str; + } else { + el.innerText = str; + } +} + +/** + * Listen on `event` with callback `fn`. + */ + +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str + .replace(re, '') + .replace(/^\s+/, ''); + + return str; +} + +}); // module: reporters/html.js + +require.register("reporters/index.js", function(module, exports, require){ + +exports.Base = require('./base'); +exports.Dot = require('./dot'); +exports.Doc = require('./doc'); +exports.TAP = require('./tap'); +exports.JSON = require('./json'); +exports.HTML = require('./html'); +exports.List = require('./list'); +exports.Min = require('./min'); +exports.Spec = require('./spec'); +exports.Progress = require('./progress'); +exports.Landing = require('./landing'); +exports.JSONCov = require('./json-cov'); +exports.HTMLCov = require('./html-cov'); +exports.JSONStream = require('./json-stream'); +exports.XUnit = require('./xunit') +exports.Teamcity = require('./teamcity') + +}); // module: reporters/index.js + +require.register("reporters/json-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @param {Boolean} output + * @api public + */ + +function JSONCov(runner, output) { + var self = this + , output = 1 == arguments.length ? true : output; + + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) return; + process.stdout.write(JSON.stringify(result, null, 2 )); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @param {Object} cov + * @return {Object} + * @api private + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage' + , sloc: 0 + , hits: 0 + , misses: 0 + , coverage: 0 + , files: [] + }; + + for (var filename in cov) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +}; + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @param {String} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + * @api private + */ + +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num){ + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line + , coverage: data[num] === undefined + ? '' + : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} + +}); // module: reporters/json-cov.js + +require.register("reporters/json-stream.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total; + + runner.on('start', function(){ + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test){ + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err){ + console.log(JSON.stringify(['fail', clean(test)])); + }); + + runner.on('end', function(){ + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json-stream.js + +require.register("reporters/json.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @param {Runner} runner + * @api public + */ + +function JSONReporter(runner) { + var self = this; + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var obj = { + stats: self.stats + , tests: tests.map(clean) + , failures: failures.map(clean) + , passes: passes.map(clean) + }; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json.js + +require.register("reporters/landing.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Landing(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , total = runner.total + , stream = process.stdout + , plane = color('plane', '✈') + , crashed = -1 + , n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function(){ + stream.write('\n '); + cursor.hide(); + }); + + runner.on('test end', function(test){ + // check if the plane crashed + var col = -1 == crashed + ? width * ++n / total | 0 + : crashed; + + // show the crash + if ('failed' == test.state) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\033[4F\n\n'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane) + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\033[0m'); + }); + + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Landing.prototype = new Base; +Landing.prototype.constructor = Landing; + +}); // module: reporters/landing.js + +require.register("reporters/list.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 0; + + runner.on('start', function(){ + console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test){ + var fmt = color('checkmark', ' ✓') + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +List.prototype = new Base; +List.prototype.constructor = List; + + +}); // module: reporters/list.js + +require.register("reporters/markdown.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Markdown(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , level = 0 + , buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function indent() { + return Array(level).join(' '); + } + + function mapTOC(suite, obj) { + var ret = obj; + obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + suite.suites.forEach(function(suite){ + mapTOC(suite, obj); + }); + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if ('suite' == key) continue; + if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + if (key) buf += Array(level).join(' ') + link; + buf += stringifyTOC(obj[key], level); + } + --level; + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite){ + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function(suite){ + --level; + }); + + runner.on('pass', function(test){ + var code = clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function(){ + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return str; +} +}); // module: reporters/markdown.js + +require.register("reporters/min.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @param {Runner} runner + * @api public + */ + +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function(){ + // clear screen + process.stdout.write('\033[2J'); + // set cursor position + process.stdout.write('\033[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Min.prototype = new Base; +Min.prototype.constructor = Min; + +}); // module: reporters/min.js + +require.register("reporters/progress.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @param {Runner} runner + * @param {Object} options + * @api public + */ + +function Progress(runner, options) { + Base.call(this, runner); + + var self = this + , options = options || {} + , stats = this.stats + , width = Base.window.width * .50 | 0 + , total = runner.total + , complete = 0 + , max = Math.max; + + // default chars + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || '⋅'; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function(){ + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function(){ + var incomplete = total - complete + , percent = complete++ / total + , n = width * percent | 0 + , i = width - n; + + cursor.CR(); + process.stdout.write('\033[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Progress.prototype = new Base; +Progress.prototype.constructor = Progress; + + +}); // module: reporters/progress.js + +require.register("reporters/spec.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Spec(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , indents = 0 + , n = 0; + + function indent() { + return Array(indents).join(' ') + } + + runner.on('start', function(){ + console.log(); + }); + + runner.on('suite', function(suite){ + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function(suite){ + --indents; + if (1 == indents) console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test){ + if ('fast' == test.speed) { + var fmt = indent() + + color('checkmark', ' ✓') + + color('pass', ' %s '); + cursor.CR(); + console.log(fmt, test.title); + } else { + var fmt = indent() + + color('checkmark', ' ✓') + + color('pass', ' %s ') + + color(test.speed, '(%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Spec.prototype = new Base; +Spec.prototype.constructor = Spec; + + +}); // module: reporters/spec.js + +require.register("reporters/tap.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @param {Runner} runner + * @api public + */ + +function TAP(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , n = 1; + + runner.on('start', function(){ + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function(){ + ++n; + }); + + runner.on('pending', function(test){ + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test){ + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err){ + console.log('not ok %d %s', n, title(test)); + console.log(err.stack.replace(/^/gm, ' ')); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @param {Object} test + * @return {String} + * @api private + */ + +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +}); // module: reporters/tap.js + +require.register("reporters/teamcity.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Teamcity(runner) { + Base.call(this, runner); + var stats = this.stats; + + runner.on('start', function() { + console.log("##teamcity[testSuiteStarted name='mocha.suite']"); + }); + + runner.on('test', function(test) { + console.log("##teamcity[testStarted name='%s']", escape(test.fullTitle())); + }); + + runner.on('fail', function(test, err) { + console.log("##teamcity[testFailed name='%s' message='%s']", escape(test.fullTitle()), escape(err.message)); + }); + + runner.on('pending', function(test) { + console.log("##teamcity[testIgnored name='%s' message='pending']", escape(test.fullTitle())); + }); + + runner.on('test end', function(test) { + console.log("##teamcity[testFinished name='%s' duration='%s']", escape(test.fullTitle()), test.duration); + }); + + runner.on('end', function() { + console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='%s']", stats.duration); + }); +} + +/** + * Escape the given `str`. + */ + +function escape(str) { + return str.replace(/'/g, "|'"); +} +}); // module: reporters/teamcity.js + +require.register("reporters/xunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , escape = utils.escape; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @param {Runner} runner + * @api public + */ + +function XUnit(runner) { + Base.call(this, runner); + var stats = this.stats + , tests = [] + , self = this; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('end', function(){ + console.log(tag('testsuite', { + name: 'Mocha Tests' + , tests: stats.tests + , failures: stats.failures + , errors: stats.failures + , skip: stats.tests - stats.failures - stats.passes + , timestamp: (new Date).toUTCString() + , time: stats.duration / 1000 + }, false)); + + tests.forEach(test); + console.log(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +XUnit.prototype = new Base; +XUnit.prototype.constructor = XUnit; + + +/** + * Output tag for the given `test.` + */ + +function test(test) { + var attrs = { + classname: test.parent.fullTitle() + , name: test.title + , time: test.duration / 1000 + }; + + if ('failed' == test.state) { + var err = test.err; + attrs.message = escape(err.message); + console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + } else if (test.pending) { + console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + console.log(tag('testcase', attrs, true) ); + } +} + +/** + * HTML tag helper. + */ + +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>' + , pairs = [] + , tag; + + for (var key in attrs) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) tag += content + ''; +} + +}); // module: reporters/xunit.js + +require.register("runnable.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('runnable'); + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = ! this.async; + this._timeout = 2000; + this.timedOut = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runnable.prototype = new EventEmitter; +Runnable.prototype.constructor = Runnable; + + +/** + * Set & get timeout `ms`. + * + * @param {Number} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) this.resetTimeout(); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Runnable.prototype.fullTitle = function(){ + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ + +Runnable.prototype.clearTimeout = function(){ + clearTimeout(this.timer); +}; + +/** + * Reset the timeout. + * + * @api private + */ + +Runnable.prototype.resetTimeout = function(){ + var self = this + , ms = this.timeout(); + + this.clearTimeout(); + if (ms) { + this.timer = setTimeout(function(){ + self.callback(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runnable.prototype.run = function(fn){ + var self = this + , ms = this.timeout() + , start = new Date + , ctx = this.ctx + , finished + , emitted; + + if (ctx) ctx.runnable(this); + + // timeout + if (this.async) { + if (ms) { + this.timer = setTimeout(function(){ + done(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } + } + + // called multiple times + function multiple() { + if (emitted) return; + emitted = true; + self.emit('error', new Error('done() called multiple times')); + } + + // finished + function done(err) { + if (self.timedOut) return; + if (finished) return multiple(); + self.clearTimeout(); + self.duration = new Date - start; + finished = true; + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // async + if (this.async) { + try { + this.fn.call(ctx, function(err){ + if (err instanceof Error) return done(err); + if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + done(); + }); + } catch (err) { + done(err); + } + return; + } + + // sync + try { + if (!this.pending) this.fn.call(ctx); + this.duration = new Date - start; + fn(); + } catch (err) { + fn(err); + } +}; + +}); // module: runnable.js + +require.register("runner.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('runner') + , Test = require('./test') + , utils = require('./utils') + , noop = function(){}; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * + * @api public + */ + +function Runner(suite) { + var self = this; + this._globals = []; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test){ self.checkGlobals(test); }); + this.on('hook end', function(hook){ self.checkGlobals(hook); }); + this.grep(/.*/); + this.globals(utils.keys(global).concat(['errno'])); +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runner.prototype = new EventEmitter; +Runner.prototype.constructor = Runner; + + +/** + * Run tests with full titles matching `re`. + * + * @param {RegExp} re + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.grep = function(re){ + debug('grep %s', re); + this._grep = re; + return this; +}; + +/** + * Allow the given `arr` of globals. + * + * @param {Array} arr + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.globals = function(arr){ + if (0 == arguments.length) return this._globals; + debug('globals %j', arr); + utils.forEach(arr, function(arr){ + this._globals.push(arr); + }, this); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ + +Runner.prototype.checkGlobals = function(test){ + if (this.ignoreLeaks) return; + + var leaks = utils.filter(utils.keys(global), function(key){ + return !~utils.indexOf(this._globals, key) && (!global.navigator || 'onerror' !== key); + }, this); + + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @param {Test} test + * @param {Error} err + * @api private + */ + +Runner.prototype.fail = function(test, err){ + ++this.failures; + test.state = 'failed'; + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures (currently) hard-end due + * to that fact that a failing hook will + * surely cause subsequent tests to fail, + * causing jumbled reporting. + * + * @param {Hook} hook + * @param {Error} err + * @api private + */ + +Runner.prototype.failHook = function(hook, err){ + this.fail(hook, err); + this.emit('end'); +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @param {String} name + * @param {Function} function + * @api private + */ + +Runner.prototype.hook = function(name, fn){ + var suite = this.suite + , hooks = suite['_' + name] + , ms = suite._timeout + , self = this + , timer; + + function next(i) { + var hook = hooks[i]; + if (!hook) return fn(); + self.currentRunnable = hook; + + self.emit('hook', hook); + + hook.on('error', function(err){ + self.failHook(hook, err); + }); + + hook.run(function(err){ + hook.removeAllListeners('error'); + if (err) return self.failHook(hook, err); + self.emit('hook end', hook); + next(++i); + }); + } + + process.nextTick(function(){ + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err)`. + * + * @param {String} name + * @param {Array} suites + * @param {Function} fn + * @api private + */ + +Runner.prototype.hooks = function(name, suites, fn){ + var self = this + , orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err){ + if (err) { + self.suite = orig; + return fn(err); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookUp = function(name, fn){ + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookDown = function(name, fn){ + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @api private + */ + +Runner.prototype.parents = function(){ + var suite = this.suite + , suites = []; + while (suite = suite.parent) suites.push(suite); + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTest = function(fn){ + var test = this.test + , self = this; + + try { + test.on('error', function(err){ + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke + * the callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTests = function(suite, fn){ + var self = this + , tests = suite.tests + , test; + + function next(err) { + // if we bail after first err + if (self.failures && suite._bail) return fn(); + + // next test + test = tests.shift(); + + // all done + if (!test) return fn(); + + // grep + if (!self._grep.test(test.fullTitle())) return next(); + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(){ + self.currentRunnable = self.test; + self.runTest(function(err){ + test = self.test; + + if (err) { + self.fail(test, err); + self.emit('test end', test); + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the + * callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runSuite = function(suite, fn){ + var self = this + , i = 0; + + debug('run suite %s', suite.fullTitle()); + this.emit('suite', this.suite = suite); + + function next() { + var curr = suite.suites[i++]; + if (!curr) return done(); + self.runSuite(curr, next); + } + + function done() { + self.suite = suite; + self.hook('afterAll', function(){ + self.emit('suite end', suite); + fn(); + }); + } + + this.hook('beforeAll', function(){ + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @param {Error} err + * @api private + */ + +Runner.prototype.uncaught = function(err){ + debug('uncaught exception'); + var runnable = this.currentRunnable; + if ('failed' == runnable.state) return; + runnable.clearTimeout(); + err.uncaught = true; + this.fail(runnable, err); + + // recover from test + if ('test' == runnable.type) { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @param {Function} fn + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.run = function(fn){ + var self = this + , fn = fn || function(){}; + + debug('start'); + + // callback + this.on('end', function(){ + debug('end'); + process.removeListener('uncaughtException', this.uncaught); + fn(self.failures); + }); + + // run suites + this.emit('start'); + this.runSuite(this.suite, function(){ + debug('finished running'); + self.emit('end'); + }); + + // uncaught exception + process.on('uncaughtException', function(err){ + self.uncaught(err); + }); + + return this; +}; + +}); // module: runner.js + +require.register("suite.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('suite') + , utils = require('./utils') + , Hook = require('./hook'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` + * and parent `Suite`. When a suite with the + * same title is already present, that suite + * is returned to provide nicer reporter + * and more flexible meta-testing. + * + * @param {Suite} parent + * @param {String} title + * @return {Suite} + * @api public + */ + +exports.create = function(parent, title){ + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given + * `title` and `ctx`. + * + * @param {String} title + * @param {Context} ctx + * @api private + */ + +function Suite(title, ctx) { + this.title = title; + this.ctx = ctx; + this.suites = []; + this.tests = []; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._bail = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Suite.prototype = new EventEmitter; +Suite.prototype.constructor = Suite; + + +/** + * Return a clone of this `Suite`. + * + * @return {Suite} + * @api private + */ + +Suite.prototype.clone = function(){ + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000; + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @parma {Boolean} bail + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.bail = function(bail){ + if (0 == arguments.length) return this._bail; + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeAll = function(fn){ + var hook = new Hook('"before all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterAll = function(fn){ + var hook = new Hook('"after all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeEach = function(fn){ + var hook = new Hook('"before each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterEach = function(fn){ + var hook = new Hook('"after each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @param {Suite} suite + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addSuite = function(suite){ + suite.parent = this; + suite.timeout(this.timeout()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @param {Test} test + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addTest = function(test){ + test.parent = this; + test.timeout(this.timeout()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Suite.prototype.fullTitle = function(){ + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) return full + ' ' + this.title; + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @return {Number} + * @api public + */ + +Suite.prototype.total = function(){ + return utils.reduce(this.suites, function(sum, suite){ + return sum + suite.total(); + }, 0) + this.tests.length; +}; + +}); // module: suite.js + +require.register("test.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Test.prototype = new Runnable; +Test.prototype.constructor = Test; + + +/** + * Inspect the context void of private properties. + * + * @return {String} + * @api private + */ + +Test.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + return '_' == key[0] + ? undefined + : 'parent' == key + ? '#' + : val; + }, 2); +}; +}); // module: test.js + +require.register("utils.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var fs = require('browser/fs') + , path = require('browser/path') + , join = path.join + , debug = require('browser/debug')('watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * 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, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope) { + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function (arr, obj, start) { + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @param {Object} scope + * @api private + */ + +exports.reduce = function(arr, fn, val, scope) { + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn.call(scope, rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.filter = function(arr, fn, scope) { + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn.call(scope, val, i, arr)) + ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ret){ + ret = ret || []; + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ret); + } else if (path.match(/\.(js|coffee)$/)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; +}); // module: utils.js diff --git a/node_modules/mocha/bin/_mocha b/node_modules/mocha/bin/_mocha new file mode 100755 index 0000000..3a7a903 --- /dev/null +++ b/node_modules/mocha/bin/_mocha @@ -0,0 +1,345 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('commander') + , exec = require('child_process').exec + , path = require('path') + , resolve = path.resolve + , mocha = require('../') + , utils = mocha.utils + , reporters = mocha.reporters + , interfaces = mocha.interfaces + , Context = mocha.Context + , Runner = mocha.Runner + , Suite = mocha.Suite + , vm = require('vm') + , fs = require('fs') + , join = path.join + , cwd = process.cwd(); + +/** + * Files. + */ + +var files = []; + +/** + * Images. + */ + +var images = { + fail: __dirname + '/../images/error.png' + , pass: __dirname + '/../images/ok.png' +}; + +// options + +program + .version(mocha.version) + .usage('[debug] [options] [files]') + .option('-r, --require ', 'require the given module') + .option('-R, --reporter ', 'specify the reporter to use', 'dot') + .option('-u, --ui ', 'specify user-interface (bdd|tdd|exports)', 'bdd') + .option('-g, --grep ', 'only run tests matching ') + .option('-t, --timeout ', 'set test-case timeout in milliseconds [2000]') + .option('-s, --slow ', '"slow" test threshold in milliseconds [75]', parseInt) + .option('-w, --watch', 'watch files for changes') + .option('-c, --colors', 'force enabling of colors') + .option('-C, --no-colors', 'force disabling of colors') + .option('-G, --growl', 'enable growl notification support') + .option('-d, --debug', "enable node's debugger, synonym for node --debug") + .option('-b, --bail', "bail after first test failure") + .option('--debug-brk', "enable node's debugger breaking on the first line") + .option('--globals ', 'allow the given comma-delimited global [names]', list, []) + .option('--ignore-leaks', 'ignore global variable leaks') + .option('--interfaces', 'display available interfaces') + .option('--reporters', 'display available reporters') + .option('--compilers :,...', 'use the given module(s) to compile files', list, []) + +program.name = 'mocha'; + +// --reporters + +program.on('reporters', function(){ + console.log(); + console.log(' dot - dot matrix'); + console.log(' doc - html documentation'); + console.log(' spec - hierarchical spec list'); + console.log(' json - single json object'); + console.log(' progress - progress bar'); + console.log(' list - spec-style listing'); + console.log(' tap - test-anything-protocol'); + console.log(' landing - unicode landing strip'); + console.log(' xunit - xunit reportert'); + console.log(' teamcity - teamcity ci support'); + console.log(' html-cov - HTML test coverage'); + console.log(' json-cov - JSON test coverage'); + console.log(' min - minimal reporter (great with --watch)'); + console.log(' json-stream - newline delimited json events'); + console.log(' markdown - markdown documentation (github flavour)'); + console.log(); + process.exit(); +}); + +// --interfaces + +program.on('interfaces', function(){ + console.log(''); + console.log(' bdd'); + console.log(' tdd'); + console.log(' qunit'); + console.log(' exports'); + console.log(''); + process.exit(); +}); + +// -r, --require + +module.paths.push(cwd, join(cwd, 'node_modules')); + +program.on('require', function(mod){ + var abs = path.existsSync(mod) + || path.existsSync(mod + '.js'); + + if (abs) mod = join(cwd, mod); + require(mod); +}); + +// mocha.opts support + +try { + var opts = fs.readFileSync('test/mocha.opts', 'utf8') + .trim() + .split(/\s+/); + + process.argv = process.argv + .slice(0, 2) + .concat(opts.concat(process.argv.slice(2))); +} catch (err) { + // ignore +} + +// parse args + +program.parse(process.argv); + +// infinite stack traces + +Error.stackTraceLimit = Infinity; // TODO: config + +// reporter + +var suite = new Suite('', new Context) + , Base = require('../lib/reporters/base') + , Reporter = require('../lib/reporters/' + program.reporter) + , ui = interfaces[program.ui](suite); + +// --no-colors + +if (!program.colors) Base.useColors = false; + +// --colors + +if (~process.argv.indexOf('--colors') || + ~process.argv.indexOf('-c')) { + Base.useColors = true; +} + +// --slow + +if (program.slow) Base.slow = program.slow; + +// --timeout + +if (program.timeout) suite.timeout(program.timeout); + +// --bail + +suite.bail(program.bail); + +// custom compiler support + +var extensions = ['js']; +program.compilers.forEach(function(c) { + var compiler = c.split(':') + , ext = compiler[0] + , mod = compiler[1]; + + if (mod[0] == '.') mod = join(process.cwd(), mod); + require(mod); + extensions.push(ext); +}); + +var re = new RegExp('\\.(' + extensions.join('|') + ')$'); + +// files + +var files = program.args; + +// default files to test/*.{js,coffee} + +if (!files.length) { + files = fs.readdirSync('test').filter(function(path){ + return path.match(re); + }).map(function(path){ + return join('test', path); + }); +} + +// resolve + +files = files.map(function(path){ + return resolve(path); +}); + +// --watch + +if (program.watch) { + console.log(); + hideCursor(); + process.on('SIGINT', function(){ + showCursor(); + console.log('\n'); + process.exit(); + }); + + var frames = [ + ' \033[96m◜ \033[90mwatching\033[0m' + , ' \033[96m◠ \033[90mwatching\033[0m' + , ' \033[96m◝ \033[90mwatching\033[0m' + , ' \033[96m◞ \033[90mwatching\033[0m' + , ' \033[96m◡ \033[90mwatching\033[0m' + , ' \033[96m◟ \033[90mwatching\033[0m' + ]; + + var watchFiles = utils.files(cwd); + + function loadAndRun() { + load(files, function(){ + run(suite, function(){ + play(frames); + }); + }); + } + + function purge() { + watchFiles.forEach(function(file){ + delete require.cache[file]; + }); + } + + loadAndRun(); + + utils.watch(watchFiles, function(){ + purge(); + stop() + suite = suite.clone(); + ui = interfaces[program.ui](suite); + loadAndRun(); + }); + + return; +} + +// load + +load(files, function(){ + run(suite, process.exit); +}); + +// require test files before +// running the root suite + +function load(files, fn) { + var pending = files.length; + files.forEach(function(file){ + delete require.cache[file]; + suite.emit('pre-require', global, file); + suite.emit('require', require(file), file); + suite.emit('post-require', global, file); + --pending || fn(); + }); +} + +// run the given suite + +function run(suite, fn) { + suite.emit('run'); + var runner = new Runner(suite); + var reporter = new Reporter(runner); + runner.globals(program.globals); + if (program.ignoreLeaks) runner.ignoreLeaks = true; + if (program.grep) runner.grep(new RegExp(program.grep)); + if (program.growl) growl(runner, reporter); + runner.run(fn); +} + +// enable growl notifications + +function growl(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { title: 'Failed', image: images.fail }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + title: 'Passed' + , image: images.pass + }); + } + }); +} + +/** + * Parse list. + */ + +function list(str) { + return str.split(/ *, */); +} + +/** + * Hide the cursor. + */ + +function hideCursor(){ + process.stdout.write('\033[?25l'); +}; + +/** + * Show the cursor. + */ + +function showCursor(){ + process.stdout.write('\033[?25h'); +}; + +/** + * Stop play()ing. + */ + +function stop() { + process.stdout.write('\033[2K'); + clearInterval(play.timer); +} + +/** + * Play the given array of strings. + */ + +function play(arr, interval) { + var len = arr.length + , interval = interval || 100 + , i = 0; + + play.timer = setInterval(function(){ + var str = arr[i++ % len]; + process.stdout.write('\r' + str); + }, interval); +} diff --git a/node_modules/mocha/bin/mocha b/node_modules/mocha/bin/mocha new file mode 100755 index 0000000..811ed9d --- /dev/null +++ b/node_modules/mocha/bin/mocha @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +/** + * This tiny wrapper file checks for known node flags and appends them + * when found, before invoking the "real" _mocha(1) executable. + */ + +var spawn = require('child_process').spawn + , args = [ __dirname + '/_mocha' ]; + +process.argv.slice(2).forEach(function (arg) { + switch (arg) { + case '-d': + args.unshift('--debug'); + break; + case 'debug': + case '--debug': + case '--debug-brk': + args.unshift(arg); + break; + case '-gc': + case '--expose-gc': + args.unshift('--expose-gc'); + break; + default: + args.push(arg); + break; + } +}); + +var proc = spawn(process.argv[0], args, { customFds: [0,1,2] }); +proc.on('exit', process.exit); diff --git a/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - after each.tmSnippet b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - after each.tmSnippet new file mode 100644 index 0000000..e76cae1 --- /dev/null +++ b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - after each.tmSnippet @@ -0,0 +1,16 @@ + + + + + content + afterEach(function(){ + $0 +}) + name + bdd - after each + tabTrigger + ae + uuid + 7B4DA8F4-2064-468B-B252-054148419B4B + + diff --git a/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - after.tmSnippet b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - after.tmSnippet new file mode 100644 index 0000000..f1a67aa --- /dev/null +++ b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - after.tmSnippet @@ -0,0 +1,16 @@ + + + + + content + after(function(){ + $0 +}) + name + bdd - after + tabTrigger + a + uuid + A49A87F9-399E-4D74-A489-C535BB06D487 + + diff --git a/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - before each.tmSnippet b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - before each.tmSnippet new file mode 100644 index 0000000..f8442fa --- /dev/null +++ b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - before each.tmSnippet @@ -0,0 +1,16 @@ + + + + + content + beforeEach(function(){ + $0 +}) + name + bdd - before each + tabTrigger + be + uuid + 7AB064E3-EFBB-4FA7-98CA-9E87C10CC04E + + diff --git a/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - before.tmSnippet b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - before.tmSnippet new file mode 100644 index 0000000..5c7e40f --- /dev/null +++ b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - before.tmSnippet @@ -0,0 +1,16 @@ + + + + + content + before(function(){ + $0 +}) + name + bdd - before + tabTrigger + b + uuid + DF6F1F42-F80A-4A24-AF78-376F19070C4C + + diff --git a/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - it.tmSnippet b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - it.tmSnippet new file mode 100644 index 0000000..8cd3fb1 --- /dev/null +++ b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/bdd - it.tmSnippet @@ -0,0 +1,16 @@ + + + + + content + it('should $1', function(){ + $0 +}) + name + bdd - it + tabTrigger + it + uuid + 591AE071-95E4-4E1E-B0F3-A7DAF41595EE + + diff --git a/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/untitled.tmSnippet b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/untitled.tmSnippet new file mode 100644 index 0000000..46fd720 --- /dev/null +++ b/node_modules/mocha/editors/JavaScript mocha.tmbundle/Snippets/untitled.tmSnippet @@ -0,0 +1,16 @@ + + + + + content + describe('$1', function(){ + $0 +}) + name + bdd - describe + tabTrigger + des + uuid + 4AA1FB50-9BB9-400E-A140-D61C39BDFDF5 + + diff --git a/node_modules/mocha/editors/JavaScript mocha.tmbundle/info.plist b/node_modules/mocha/editors/JavaScript mocha.tmbundle/info.plist new file mode 100644 index 0000000..16f6587 --- /dev/null +++ b/node_modules/mocha/editors/JavaScript mocha.tmbundle/info.plist @@ -0,0 +1,19 @@ + + + + + name + JavaScript mocha + ordering + + 4AA1FB50-9BB9-400E-A140-D61C39BDFDF5 + 591AE071-95E4-4E1E-B0F3-A7DAF41595EE + DF6F1F42-F80A-4A24-AF78-376F19070C4C + A49A87F9-399E-4D74-A489-C535BB06D487 + 7AB064E3-EFBB-4FA7-98CA-9E87C10CC04E + 7B4DA8F4-2064-468B-B252-054148419B4B + + uuid + 094ACE33-0C0E-422A-B3F7-5B919F5B1239 + + diff --git a/node_modules/mocha/images/error.png b/node_modules/mocha/images/error.png new file mode 100644 index 0000000..490822a Binary files /dev/null and b/node_modules/mocha/images/error.png differ diff --git a/node_modules/mocha/images/ok.png b/node_modules/mocha/images/ok.png new file mode 100644 index 0000000..2983c0b Binary files /dev/null and b/node_modules/mocha/images/ok.png differ diff --git a/node_modules/mocha/index.js b/node_modules/mocha/index.js new file mode 100644 index 0000000..507566f --- /dev/null +++ b/node_modules/mocha/index.js @@ -0,0 +1,4 @@ + +module.exports = process.env.COV + ? require('./lib-cov/mocha') + : require('./lib/mocha'); \ No newline at end of file diff --git a/node_modules/mocha/lib/browser/debug.js b/node_modules/mocha/lib/browser/debug.js new file mode 100644 index 0000000..946f4ab --- /dev/null +++ b/node_modules/mocha/lib/browser/debug.js @@ -0,0 +1,6 @@ + +module.exports = function(type){ + return function(){ + + } +}; \ No newline at end of file diff --git a/node_modules/mocha/lib/browser/diff.js b/node_modules/mocha/lib/browser/diff.js new file mode 100644 index 0000000..e69de29 diff --git a/node_modules/mocha/lib/browser/events.js b/node_modules/mocha/lib/browser/events.js new file mode 100644 index 0000000..4d94ee1 --- /dev/null +++ b/node_modules/mocha/lib/browser/events.js @@ -0,0 +1,178 @@ + +/** + * Module exports. + */ + +exports.EventEmitter = EventEmitter; + +/** + * Check if `obj` is an array. + */ + +function isArray(obj) { + return '[object Array]' == {}.toString.call(obj); +} + +/** + * 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 (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 (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) { + 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 (!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 = [].slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (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; +}; \ No newline at end of file diff --git a/node_modules/mocha/lib/browser/fs.js b/node_modules/mocha/lib/browser/fs.js new file mode 100644 index 0000000..e69de29 diff --git a/node_modules/mocha/lib/browser/path.js b/node_modules/mocha/lib/browser/path.js new file mode 100644 index 0000000..e69de29 diff --git a/node_modules/mocha/lib/browser/progress.js b/node_modules/mocha/lib/browser/progress.js new file mode 100644 index 0000000..1d60cde --- /dev/null +++ b/node_modules/mocha/lib/browser/progress.js @@ -0,0 +1,125 @@ + +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ + +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.size = function(n){ + this._size = n; + return this; +}; + +/** + * Set text to `str`. + * + * @param {String} str + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.text = function(str){ + this._text = str; + return this; +}; + +/** + * Set font size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.fontSize = function(n){ + this._fontSize = n; + return this; +}; + +/** + * Set font `family`. + * + * @param {String} family + * @return {Progress} for chaining + */ + +Progress.prototype.font = function(family){ + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + */ + +Progress.prototype.update = function(n){ + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} for chaining + */ + +Progress.prototype.draw = function(ctx){ + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + + return this; +}; diff --git a/node_modules/mocha/lib/browser/tty.js b/node_modules/mocha/lib/browser/tty.js new file mode 100644 index 0000000..2d84374 --- /dev/null +++ b/node_modules/mocha/lib/browser/tty.js @@ -0,0 +1,8 @@ + +exports.isatty = function(){ + return true; +}; + +exports.getWindowSize = function(){ + return [window.innerHeight, window.innerWidth]; +}; \ No newline at end of file diff --git a/node_modules/mocha/lib/context.js b/node_modules/mocha/lib/context.js new file mode 100644 index 0000000..f636f1b --- /dev/null +++ b/node_modules/mocha/lib/context.js @@ -0,0 +1,55 @@ + +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @api private + */ + +function Context(){} + +/** + * Set the context `Runnable` to `runnable`. + * + * @param {Runnable} runnable + * @return {Context} + * @api private + */ + +Context.prototype.runnable = function(runnable){ + this._runnable = runnable; + return this; +}; + +/** + * Set test timeout `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.timeout = function(ms){ + this._runnable.timeout(ms); + return this; +}; + +/** + * Inspect the context void of `._runnable`. + * + * @return {String} + * @api private + */ + +Context.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + return '_runnable' == key + ? undefined + : val; + }, 2); +}; diff --git a/node_modules/mocha/lib/hook.js b/node_modules/mocha/lib/hook.js new file mode 100644 index 0000000..3ed712e --- /dev/null +++ b/node_modules/mocha/lib/hook.js @@ -0,0 +1,31 @@ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Hook.prototype.__proto__ = Runnable.prototype; diff --git a/node_modules/mocha/lib/interfaces/bdd.js b/node_modules/mocha/lib/interfaces/bdd.js new file mode 100644 index 0000000..5f66e0e --- /dev/null +++ b/node_modules/mocha/lib/interfaces/bdd.js @@ -0,0 +1,91 @@ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * BDD-style interface: + * + * describe('Array', function(){ + * describe('#indexOf()', function(){ + * it('should return -1 when not present', function(){ + * + * }); + * + * it('should return the index when present', function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + // noop variants + + context.xdescribe = function(){}; + context.xit = function(){}; + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn(); + suites.shift(); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; diff --git a/node_modules/mocha/lib/interfaces/exports.js b/node_modules/mocha/lib/interfaces/exports.js new file mode 100644 index 0000000..b22607e --- /dev/null +++ b/node_modules/mocha/lib/interfaces/exports.js @@ -0,0 +1,60 @@ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function(){ + * + * }, + * + * 'should return the correct index when the value is present': function(){ + * + * } + * } + * }; + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('require', visit); + + function visit(obj) { + var suite; + for (var key in obj) { + if ('function' == typeof obj[key]) { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + suites[0].addTest(new Test(key, fn)); + } + } else { + var suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key]); + suites.shift(); + } + } + } +}; \ No newline at end of file diff --git a/node_modules/mocha/lib/interfaces/index.js b/node_modules/mocha/lib/interfaces/index.js new file mode 100644 index 0000000..f7b2655 --- /dev/null +++ b/node_modules/mocha/lib/interfaces/index.js @@ -0,0 +1,5 @@ + +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); diff --git a/node_modules/mocha/lib/interfaces/qunit.js b/node_modules/mocha/lib/interfaces/qunit.js new file mode 100644 index 0000000..cd03ab6 --- /dev/null +++ b/node_modules/mocha/lib/interfaces/qunit.js @@ -0,0 +1,91 @@ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function(){ + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function(){ + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function(){ + * ok('foo'.length == 3); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; diff --git a/node_modules/mocha/lib/interfaces/tdd.js b/node_modules/mocha/lib/interfaces/tdd.js new file mode 100644 index 0000000..53c51b8 --- /dev/null +++ b/node_modules/mocha/lib/interfaces/tdd.js @@ -0,0 +1,94 @@ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * suite('Array', function(){ + * suite('#indexOf()', function(){ + * suiteSetup(function(){ + * + * }); + * + * test('should return -1 when not present', function(){ + * + * }); + * + * test('should return the index when present', function(){ + * + * }); + * + * suiteTeardown(function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + /** + * Execute before each test case. + */ + + context.setup = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.teardown = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Execute before the suite. + */ + + context.suiteSetup = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after the suite. + */ + + context.suiteTeardown = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.suite = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn(); + suites.shift(); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; diff --git a/node_modules/mocha/lib/mocha.js b/node_modules/mocha/lib/mocha.js new file mode 100644 index 0000000..71d96f8 --- /dev/null +++ b/node_modules/mocha/lib/mocha.js @@ -0,0 +1,176 @@ + +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('path'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * Library version. + */ + +exports.version = '1.0.1'; + +/** + * Expose internals. + */ + +exports.utils = require('./utils'); +exports.interfaces = require('./interfaces'); +exports.reporters = require('./reporters'); +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @param {String} name + * @return {String} + * @api private + */ + +function image(name) { + return __dirname + '/../images/' + name + '.png'; +} + +/** + * Setup mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `ignoreLeaks` ignore global leaks + * + * @param {Object} options + * @api public + */ + +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + this.suite = new exports.Suite('', new exports.Context); + this.ui(options.ui); + this.reporter(options.reporter); + if (options.timeout) this.suite.timeout(options.timeout); +} + +/** + * Add test `file`. + * + * @param {String} file + * @api public + */ + +Mocha.prototype.addFile = function(file){ + this.files.push(file); + return this; +}; + +/** + * Set reporter to `name`, defaults to "dot". + * + * @param {String} name + * @api public + */ + +Mocha.prototype.reporter = function(name){ + name = name || 'dot'; + this._reporter = require('./reporters/' + name); + if (!this._reporter) throw new Error('invalid reporter "' + name + '"'); + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @param {String} bdd + * @api public + */ + +Mocha.prototype.ui = function(name){ + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) throw new Error('invalid interface "' + name + '"'); + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ + +Mocha.prototype.loadFiles = function(){ + var suite = this.suite; + this.files.forEach(function(file){ + file = path.resolve(file); + suite.emit('pre-require', global, file); + suite.emit('require', require(file), file); + suite.emit('post-require', global, file); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ + +Mocha.prototype.growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { title: 'Failed', image: image('fail') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + title: 'Passed' + , image: image('pass') + }); + } + }); +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @param {Function} fn + * @return {Runner} + * @api public + */ + +Mocha.prototype.run = function(fn){ + this.loadFiles(); + var suite = this.suite; + var options = this.options; + var runner = new exports.Runner(suite); + var reporter = new this._reporter(runner); + runner.ignoreLeaks = options.ignoreLeaks; + if (options.grep) runner.grep(options.grep); + if (options.globals) runner.globals(options.globals); + if (options.growl) this.growl(runner, reporter); + return runner.run(fn); +}; diff --git a/node_modules/mocha/lib/reporters/base.js b/node_modules/mocha/lib/reporters/base.js new file mode 100644 index 0000000..fddd0bb --- /dev/null +++ b/node_modules/mocha/lib/reporters/base.js @@ -0,0 +1,319 @@ + +/** + * Module dependencies. + */ + +var tty = require('tty') + , diff = require('diff'); + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Enable coloring by default. + */ + +exports.useColors = isatty; + +/** + * Default color map. + */ + +exports.colors = { + 'pass': 90 + , 'fail': 31 + , 'bright pass': 92 + , 'bright fail': 91 + , 'bright yellow': 93 + , 'pending': 36 + , 'suite': 0 + , 'error title': 0 + , 'error message': 31 + , 'error stack': 90 + , 'checkmark': 32 + , 'fast': 90 + , 'medium': 33 + , 'slow': 31 + , 'green': 32 + , 'light': 90 + , 'diff gutter': 90 + , 'diff added': 42 + , 'diff removed': 41 +}; + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {String} type + * @param {String} str + * @return {String} + * @api private + */ + +var color = exports.color = function(type, str) { + if (!exports.useColors) return str; + return '\033[' + exports.colors[type] + 'm' + str + '\033[0m'; +}; + +/** + * Expose term window size, with some + * defaults for when stderr is not a tty. + */ + +exports.window = { + width: isatty + ? process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1] + : 75 +}; + +/** + * Expose some basic cursor interactions + * that are common among reporters. + */ + +exports.cursor = { + hide: function(){ + process.stdout.write('\033[?25l'); + }, + + show: function(){ + process.stdout.write('\033[?25h'); + }, + + deleteLine: function(){ + process.stdout.write('\033[2K'); + }, + + beginningOfLine: function(){ + process.stdout.write('\033[0G'); + }, + + CR: function(){ + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } +}; + +/** + * A test is considered slow if it + * exceeds the following value in milliseconds. + */ + +exports.slow = 75; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures){ + console.error(); + failures.forEach(function(test, i){ + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var err = test.err + , message = err.message || '' + , stack = err.stack || message + , index = stack.indexOf(message) + message.length + , msg = stack.slice(0, index) + , actual = err.actual + , expected = err.expected; + + // actual / expected diff + if ('string' == typeof actual && 'string' == typeof expected) { + var len = Math.max(actual.length, expected.length); + + if (len < 20) msg = errorDiff(err, 'Chars'); + else msg = errorDiff(err, 'Words'); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + + fmt = color('error title', ' %s) %s:\n%s') + + color('error stack', '\n%s\n'); + } + + // indent stack trace without msg + stack = stack.slice(index ? index + 1 : index) + .replace(/^/gm, ' '); + + console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var self = this + , stats = this.stats = { suites: 0, tests: 0, passes: 0, failures: 0 } + , failures = this.failures = []; + + if (!runner) return; + this.runner = runner; + + runner.on('start', function(){ + stats.start = new Date; + }); + + runner.on('suite', function(suite){ + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function(test){ + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test){ + stats.passes = stats.passes || 0; + + var medium = exports.slow / 2; + test.speed = test.duration > exports.slow + ? 'slow' + : test.duration > medium + ? 'medium' + : 'fast'; + + stats.passes++; + }); + + runner.on('fail', function(test, err){ + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function(){ + stats.end = new Date; + stats.duration = new Date - stats.start; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ + +Base.prototype.epilogue = function(){ + var stats = this.stats + , fmt; + + console.log(); + + // failure + if (stats.failures) { + fmt = color('bright fail', ' ✖') + + color('fail', ' %d of %d tests failed') + + color('light', ':') + + console.error(fmt, stats.failures, this.runner.total); + Base.list(this.failures); + console.error(); + return; + } + + // pass + fmt = color('bright pass', ' ✔') + + color('green', ' %d tests complete') + + color('light', ' (%dms)'); + + console.log(fmt, stats.tests || 0, stats.duration); + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @param {String} str + * @param {String} len + * @return {String} + * @api private + */ + +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Return a character diff for `err`. + * + * @param {Error} err + * @return {String} + * @api private + */ + +function errorDiff(err, type) { + return diff['diff' + type](err.actual, err.expected).map(function(str){ + if (str.added) return colorLines('diff added', str.value); + if (str.removed) return colorLines('diff removed', str.value); + return str.value; + }).join(''); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @param {String} name + * @param {String} str + * @return {String} + * @api private + */ + +function colorLines(name, str) { + return str.split('\n').map(function(str){ + return color(name, str); + }).join('\n'); +} diff --git a/node_modules/mocha/lib/reporters/doc.js b/node_modules/mocha/lib/reporters/doc.js new file mode 100644 index 0000000..cf7e712 --- /dev/null +++ b/node_modules/mocha/lib/reporters/doc.js @@ -0,0 +1,74 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Doc(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite){ + if (suite.root) return; + ++indents; + console.log('%s
', indent()); + ++indents; + console.log('%s

%s

', indent(), suite.title); + console.log('%s
', indent()); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + console.log('%s
', indent()); + --indents; + console.log('%s
', indent()); + --indents; + }); + + runner.on('pass', function(test){ + console.log('%s
%s
', indent(), test.title); + var code = utils.escape(clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + }); +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return str; +} \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/dot.js b/node_modules/mocha/lib/reporters/dot.js new file mode 100644 index 0000000..20158db --- /dev/null +++ b/node_modules/mocha/lib/reporters/dot.js @@ -0,0 +1,62 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Dot(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , n = 0; + + runner.on('start', function(){ + process.stdout.write('\n '); + }); + + runner.on('pending', function(test){ + process.stdout.write(color('pending', '.')); + }); + + runner.on('pass', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + if ('slow' == test.speed) { + process.stdout.write(color('bright yellow', '.')); + } else { + process.stdout.write(color(test.speed, '.')); + } + }); + + runner.on('fail', function(test, err){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('fail', '.')); + }); + + runner.on('end', function(){ + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Dot.prototype.__proto__ = Base.prototype; \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/html-cov.js b/node_modules/mocha/lib/reporters/html-cov.js new file mode 100644 index 0000000..6669bb0 --- /dev/null +++ b/node_modules/mocha/lib/reporters/html-cov.js @@ -0,0 +1,44 @@ + +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov') + , fs = require('fs'); + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTMLCov(runner) { + var jade = require('jade') + , file = __dirname + '/templates/coverage.jade' + , str = fs.readFileSync(file, 'utf8') + , fn = jade.compile(str, { filename: file }) + , self = this; + + JSONCov.call(this, runner, false); + + runner.on('end', function(){ + process.stdout.write(fn({ + cov: self.cov + , coverageClass: coverageClass + })); + }); +} + +function coverageClass(n) { + if (n >= 75) return 'high'; + if (n >= 50) return 'medium'; + if (n >= 25) return 'low'; + return 'terrible'; +} \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/html.js b/node_modules/mocha/lib/reporters/html.js new file mode 100644 index 0000000..2c3791c --- /dev/null +++ b/node_modules/mocha/lib/reporters/html.js @@ -0,0 +1,211 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , Progress = require('../browser/progress') + , escape = utils.escape; + +/** + * Expose `Doc`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = '
    ' + + '
  • ' + + '
  • passes: 0
  • ' + + '
  • failures: 0
  • ' + + '
  • duration: 0s
  • ' + + '
'; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTML(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , root = document.getElementById('mocha') + , stat = fragment(statsTemplate) + , items = stat.getElementsByTagName('li') + , passes = items[1].getElementsByTagName('em')[0] + , failures = items[2].getElementsByTagName('em')[0] + , duration = items[3].getElementsByTagName('em')[0] + , canvas = stat.getElementsByTagName('canvas')[0] + , stack = [root] + , progress + , ctx + + if (canvas.getContext) { + ctx = canvas.getContext('2d'); + progress = new Progress; + } + + if (!root) return error('#mocha div missing, add it to your document'); + + root.appendChild(stat); + + if (progress) progress.size(40); + + runner.on('suite', function(suite){ + if (suite.root) return; + + // suite + var el = fragment('

%s

', suite.title); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('div')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + stack.shift(); + }); + + runner.on('fail', function(test, err){ + if ('hook' == test.type || err.uncaught) runner.emit('test end', test); + }); + + runner.on('test end', function(test){ + // TODO: add to stats + var percent = stats.tests / total * 100 | 0; + if (progress) progress.update(percent).draw(ctx); + + // update stats + var ms = new Date - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + + // test + if ('passed' == test.state) { + var el = fragment('

%e

', test.title); + } else if (test.pending) { + var el = fragment('

%e

', test.title); + } else { + var el = fragment('

%e

', test.title); + var str = test.err.stack || test.err.toString(); + + // FF / Opera do not add the message + if (!~str.indexOf(test.err.message)) { + str = test.err.message + '\n' + str; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == str) str = test.err.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { + str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; + } + + el.appendChild(fragment('
%e
', str)); + } + + // toggle code + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function(){ + pre.style.display = 'none' == pre.style.display + ? 'block' + : 'none'; + }); + + // code + // TODO: defer + if (!test.pending) { + var pre = fragment('
%e
', clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + stack[0].appendChild(el); + }); +} + +/** + * Display error `msg`. + */ + +function error(msg) { + document.body.appendChild(fragment('
%s
', msg)); +} + +/** + * Return a DOM fragment from `html`. + */ + +function fragment(html) { + var args = arguments + , div = document.createElement('div') + , i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type){ + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + } + }); + + return div.firstChild; +} + +/** + * Set `el` text to `str`. + */ + +function text(el, str) { + if (el.textContent) { + el.textContent = str; + } else { + el.innerText = str; + } +} + +/** + * Listen on `event` with callback `fn`. + */ + +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str + .replace(re, '') + .replace(/^\s+/, ''); + + return str; +} diff --git a/node_modules/mocha/lib/reporters/index.js b/node_modules/mocha/lib/reporters/index.js new file mode 100644 index 0000000..d7b5de7 --- /dev/null +++ b/node_modules/mocha/lib/reporters/index.js @@ -0,0 +1,17 @@ + +exports.Base = require('./base'); +exports.Dot = require('./dot'); +exports.Doc = require('./doc'); +exports.TAP = require('./tap'); +exports.JSON = require('./json'); +exports.HTML = require('./html'); +exports.List = require('./list'); +exports.Min = require('./min'); +exports.Spec = require('./spec'); +exports.Progress = require('./progress'); +exports.Landing = require('./landing'); +exports.JSONCov = require('./json-cov'); +exports.HTMLCov = require('./html-cov'); +exports.JSONStream = require('./json-stream'); +exports.XUnit = require('./xunit') +exports.Teamcity = require('./teamcity') diff --git a/node_modules/mocha/lib/reporters/json-cov.js b/node_modules/mocha/lib/reporters/json-cov.js new file mode 100644 index 0000000..7eeab30 --- /dev/null +++ b/node_modules/mocha/lib/reporters/json-cov.js @@ -0,0 +1,149 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @param {Boolean} output + * @api public + */ + +function JSONCov(runner, output) { + var self = this + , output = 1 == arguments.length ? true : output; + + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) return; + process.stdout.write(JSON.stringify(result, null, 2 )); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @param {Object} cov + * @return {Object} + * @api private + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage' + , sloc: 0 + , hits: 0 + , misses: 0 + , coverage: 0 + , files: [] + }; + + for (var filename in cov) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +}; + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @param {String} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + * @api private + */ + +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num){ + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line + , coverage: data[num] === undefined + ? '' + : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} diff --git a/node_modules/mocha/lib/reporters/json-stream.js b/node_modules/mocha/lib/reporters/json-stream.js new file mode 100644 index 0000000..7cb8fbe --- /dev/null +++ b/node_modules/mocha/lib/reporters/json-stream.js @@ -0,0 +1,61 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total; + + runner.on('start', function(){ + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test){ + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err){ + console.log(JSON.stringify(['fail', clean(test)])); + }); + + runner.on('end', function(){ + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/json.js b/node_modules/mocha/lib/reporters/json.js new file mode 100644 index 0000000..a699f50 --- /dev/null +++ b/node_modules/mocha/lib/reporters/json.js @@ -0,0 +1,70 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @param {Runner} runner + * @api public + */ + +function JSONReporter(runner) { + var self = this; + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var obj = { + stats: self.stats + , tests: tests.map(clean) + , failures: failures.map(clean) + , passes: passes.map(clean) + }; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/landing.js b/node_modules/mocha/lib/reporters/landing.js new file mode 100644 index 0000000..8d59e50 --- /dev/null +++ b/node_modules/mocha/lib/reporters/landing.js @@ -0,0 +1,97 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Landing(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , total = runner.total + , stream = process.stdout + , plane = color('plane', '✈') + , crashed = -1 + , n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function(){ + stream.write('\n '); + cursor.hide(); + }); + + runner.on('test end', function(test){ + // check if the plane crashed + var col = -1 == crashed + ? width * ++n / total | 0 + : crashed; + + // show the crash + if ('failed' == test.state) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\033[4F\n\n'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane) + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\033[0m'); + }); + + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Landing.prototype.__proto__ = Base.prototype; \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/list.js b/node_modules/mocha/lib/reporters/list.js new file mode 100644 index 0000000..1a88989 --- /dev/null +++ b/node_modules/mocha/lib/reporters/list.js @@ -0,0 +1,64 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 0; + + runner.on('start', function(){ + console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test){ + var fmt = color('checkmark', ' ✓') + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +List.prototype.__proto__ = Base.prototype; diff --git a/node_modules/mocha/lib/reporters/markdown.js b/node_modules/mocha/lib/reporters/markdown.js new file mode 100644 index 0000000..7eeae85 --- /dev/null +++ b/node_modules/mocha/lib/reporters/markdown.js @@ -0,0 +1,111 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Markdown(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , level = 0 + , buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function indent() { + return Array(level).join(' '); + } + + function mapTOC(suite, obj) { + var ret = obj; + obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + suite.suites.forEach(function(suite){ + mapTOC(suite, obj); + }); + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if ('suite' == key) continue; + if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + if (key) buf += Array(level).join(' ') + link; + buf += stringifyTOC(obj[key], level); + } + --level; + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite){ + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '
' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function(suite){ + --level; + }); + + runner.on('pass', function(test){ + var code = clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function(){ + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return str; +} \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/min.js b/node_modules/mocha/lib/reporters/min.js new file mode 100644 index 0000000..9f3cea1 --- /dev/null +++ b/node_modules/mocha/lib/reporters/min.js @@ -0,0 +1,37 @@ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @param {Runner} runner + * @api public + */ + +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function(){ + // clear screen + process.stdout.write('\033[2J'); + // set cursor position + process.stdout.write('\033[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Min.prototype.__proto__ = Base.prototype; \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/progress.js b/node_modules/mocha/lib/reporters/progress.js new file mode 100644 index 0000000..311c8b7 --- /dev/null +++ b/node_modules/mocha/lib/reporters/progress.js @@ -0,0 +1,85 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @param {Runner} runner + * @param {Object} options + * @api public + */ + +function Progress(runner, options) { + Base.call(this, runner); + + var self = this + , options = options || {} + , stats = this.stats + , width = Base.window.width * .50 | 0 + , total = runner.total + , complete = 0 + , max = Math.max; + + // default chars + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || '⋅'; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function(){ + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function(){ + var incomplete = total - complete + , percent = complete++ / total + , n = width * percent | 0 + , i = width - n; + + cursor.CR(); + process.stdout.write('\033[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Progress.prototype.__proto__ = Base.prototype; diff --git a/node_modules/mocha/lib/reporters/spec.js b/node_modules/mocha/lib/reporters/spec.js new file mode 100644 index 0000000..e546314 --- /dev/null +++ b/node_modules/mocha/lib/reporters/spec.js @@ -0,0 +1,87 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Spec(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , indents = 0 + , n = 0; + + function indent() { + return Array(indents).join(' ') + } + + runner.on('start', function(){ + console.log(); + }); + + runner.on('suite', function(suite){ + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function(suite){ + --indents; + if (1 == indents) console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test){ + if ('fast' == test.speed) { + var fmt = indent() + + color('checkmark', ' ✓') + + color('pass', ' %s '); + cursor.CR(); + console.log(fmt, test.title); + } else { + var fmt = indent() + + color('checkmark', ' ✓') + + color('pass', ' %s ') + + color(test.speed, '(%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Spec.prototype.__proto__ = Base.prototype; diff --git a/node_modules/mocha/lib/reporters/tap.js b/node_modules/mocha/lib/reporters/tap.js new file mode 100644 index 0000000..e601dc9 --- /dev/null +++ b/node_modules/mocha/lib/reporters/tap.js @@ -0,0 +1,63 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @param {Runner} runner + * @api public + */ + +function TAP(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , n = 1; + + runner.on('start', function(){ + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function(){ + ++n; + }); + + runner.on('pending', function(test){ + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test){ + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err){ + console.log('not ok %d %s', n, title(test)); + console.log(err.stack.replace(/^/gm, ' ')); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @param {Object} test + * @return {String} + * @api private + */ + +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} diff --git a/node_modules/mocha/lib/reporters/teamcity.js b/node_modules/mocha/lib/reporters/teamcity.js new file mode 100644 index 0000000..cef71c8 --- /dev/null +++ b/node_modules/mocha/lib/reporters/teamcity.js @@ -0,0 +1,56 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Teamcity(runner) { + Base.call(this, runner); + var stats = this.stats; + + runner.on('start', function() { + console.log("##teamcity[testSuiteStarted name='mocha.suite']"); + }); + + runner.on('test', function(test) { + console.log("##teamcity[testStarted name='%s']", escape(test.fullTitle())); + }); + + runner.on('fail', function(test, err) { + console.log("##teamcity[testFailed name='%s' message='%s']", escape(test.fullTitle()), escape(err.message)); + }); + + runner.on('pending', function(test) { + console.log("##teamcity[testIgnored name='%s' message='pending']", escape(test.fullTitle())); + }); + + runner.on('test end', function(test) { + console.log("##teamcity[testFinished name='%s' duration='%s']", escape(test.fullTitle()), test.duration); + }); + + runner.on('end', function() { + console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='%s']", stats.duration); + }); +} + +/** + * Escape the given `str`. + */ + +function escape(str) { + return str.replace(/'/g, "|'"); +} \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/templates/coverage.jade b/node_modules/mocha/lib/reporters/templates/coverage.jade new file mode 100644 index 0000000..ca842ed --- /dev/null +++ b/node_modules/mocha/lib/reporters/templates/coverage.jade @@ -0,0 +1,50 @@ +!!! 5 +html + head + title Coverage + include script.html + include style.html + body + #coverage + h1#overview Coverage + include menu + + #stats(class=coverageClass(cov.coverage)) + .percentage #{cov.coverage | 0}% + .sloc= cov.sloc + .hits= cov.hits + .misses= cov.misses + + #files + for file in cov.files + .file + h2(id=file.filename)= file.filename + #stats(class=coverageClass(file.coverage)) + .percentage #{file.coverage | 0}% + .sloc= file.sloc + .hits= file.hits + .misses= file.misses + + table#source + thead + tr + th Line + th Hits + th Source + tbody + for line, number in file.source + if line.coverage > 0 + tr.hit + td.line= number + td.hits= line.coverage + td.source= line.source + else if 0 === line.coverage + tr.miss + td.line= number + td.hits 0 + td.source= line.source + else + tr + td.line= number + td.hits + td.source= line.source || ' ' \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/templates/menu.jade b/node_modules/mocha/lib/reporters/templates/menu.jade new file mode 100644 index 0000000..25cd1fa --- /dev/null +++ b/node_modules/mocha/lib/reporters/templates/menu.jade @@ -0,0 +1,13 @@ +#menu + li + a(href='#overview') overview + for file in cov.files + li + span.cov(class=coverageClass(file.coverage)) #{file.coverage | 0} + a(href='##{file.filename}') + segments = file.filename.split('/') + basename = segments.pop() + if segments.length + span.dirname= segments.join('/') + '/' + span.basename= basename + a#logo(href='http://visionmedia.github.com/mocha/') m \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/templates/script.html b/node_modules/mocha/lib/reporters/templates/script.html new file mode 100644 index 0000000..7df9bcd --- /dev/null +++ b/node_modules/mocha/lib/reporters/templates/script.html @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/templates/style.html b/node_modules/mocha/lib/reporters/templates/style.html new file mode 100644 index 0000000..c4dfe2a --- /dev/null +++ b/node_modules/mocha/lib/reporters/templates/style.html @@ -0,0 +1,301 @@ + \ No newline at end of file diff --git a/node_modules/mocha/lib/reporters/xunit.js b/node_modules/mocha/lib/reporters/xunit.js new file mode 100644 index 0000000..63bcbd4 --- /dev/null +++ b/node_modules/mocha/lib/reporters/xunit.js @@ -0,0 +1,101 @@ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , escape = utils.escape; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @param {Runner} runner + * @api public + */ + +function XUnit(runner) { + Base.call(this, runner); + var stats = this.stats + , tests = [] + , self = this; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('end', function(){ + console.log(tag('testsuite', { + name: 'Mocha Tests' + , tests: stats.tests + , failures: stats.failures + , errors: stats.failures + , skip: stats.tests - stats.failures - stats.passes + , timestamp: (new Date).toUTCString() + , time: stats.duration / 1000 + }, false)); + + tests.forEach(test); + console.log(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +XUnit.prototype.__proto__ = Base.prototype; + +/** + * Output tag for the given `test.` + */ + +function test(test) { + var attrs = { + classname: test.parent.fullTitle() + , name: test.title + , time: test.duration / 1000 + }; + + if ('failed' == test.state) { + var err = test.err; + attrs.message = escape(err.message); + console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + } else if (test.pending) { + console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + console.log(tag('testcase', attrs, true) ); + } +} + +/** + * HTML tag helper. + */ + +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>' + , pairs = [] + , tag; + + for (var key in attrs) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) tag += content + ''; +} diff --git a/node_modules/mocha/lib/runnable.js b/node_modules/mocha/lib/runnable.js new file mode 100644 index 0000000..beb4691 --- /dev/null +++ b/node_modules/mocha/lib/runnable.js @@ -0,0 +1,164 @@ + +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , debug = require('debug')('runnable'); + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = ! this.async; + this._timeout = 2000; + this.timedOut = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runnable.prototype.__proto__ = EventEmitter.prototype; + +/** + * Set & get timeout `ms`. + * + * @param {Number} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) this.resetTimeout(); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Runnable.prototype.fullTitle = function(){ + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ + +Runnable.prototype.clearTimeout = function(){ + clearTimeout(this.timer); +}; + +/** + * Reset the timeout. + * + * @api private + */ + +Runnable.prototype.resetTimeout = function(){ + var self = this + , ms = this.timeout(); + + this.clearTimeout(); + if (ms) { + this.timer = setTimeout(function(){ + self.callback(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runnable.prototype.run = function(fn){ + var self = this + , ms = this.timeout() + , start = new Date + , ctx = this.ctx + , finished + , emitted; + + if (ctx) ctx.runnable(this); + + // timeout + if (this.async) { + if (ms) { + this.timer = setTimeout(function(){ + done(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } + } + + // called multiple times + function multiple() { + if (emitted) return; + emitted = true; + self.emit('error', new Error('done() called multiple times')); + } + + // finished + function done(err) { + if (self.timedOut) return; + if (finished) return multiple(); + self.clearTimeout(); + self.duration = new Date - start; + finished = true; + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // async + if (this.async) { + try { + this.fn.call(ctx, function(err){ + if (err instanceof Error) return done(err); + if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + done(); + }); + } catch (err) { + done(err); + } + return; + } + + // sync + try { + if (!this.pending) this.fn.call(ctx); + this.duration = new Date - start; + fn(); + } catch (err) { + fn(err); + } +}; diff --git a/node_modules/mocha/lib/runner.js b/node_modules/mocha/lib/runner.js new file mode 100644 index 0000000..c836d6b --- /dev/null +++ b/node_modules/mocha/lib/runner.js @@ -0,0 +1,431 @@ + +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , debug = require('debug')('runner') + , Test = require('./test') + , utils = require('./utils') + , noop = function(){}; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * + * @api public + */ + +function Runner(suite) { + var self = this; + this._globals = []; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test){ self.checkGlobals(test); }); + this.on('hook end', function(hook){ self.checkGlobals(hook); }); + this.grep(/.*/); + this.globals(utils.keys(global).concat(['errno'])); +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runner.prototype.__proto__ = EventEmitter.prototype; + +/** + * Run tests with full titles matching `re`. + * + * @param {RegExp} re + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.grep = function(re){ + debug('grep %s', re); + this._grep = re; + return this; +}; + +/** + * Allow the given `arr` of globals. + * + * @param {Array} arr + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.globals = function(arr){ + if (0 == arguments.length) return this._globals; + debug('globals %j', arr); + utils.forEach(arr, function(arr){ + this._globals.push(arr); + }, this); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ + +Runner.prototype.checkGlobals = function(test){ + if (this.ignoreLeaks) return; + + var leaks = utils.filter(utils.keys(global), function(key){ + return !~utils.indexOf(this._globals, key) && (!global.navigator || 'onerror' !== key); + }, this); + + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @param {Test} test + * @param {Error} err + * @api private + */ + +Runner.prototype.fail = function(test, err){ + ++this.failures; + test.state = 'failed'; + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures (currently) hard-end due + * to that fact that a failing hook will + * surely cause subsequent tests to fail, + * causing jumbled reporting. + * + * @param {Hook} hook + * @param {Error} err + * @api private + */ + +Runner.prototype.failHook = function(hook, err){ + this.fail(hook, err); + this.emit('end'); +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @param {String} name + * @param {Function} function + * @api private + */ + +Runner.prototype.hook = function(name, fn){ + var suite = this.suite + , hooks = suite['_' + name] + , ms = suite._timeout + , self = this + , timer; + + function next(i) { + var hook = hooks[i]; + if (!hook) return fn(); + self.currentRunnable = hook; + + self.emit('hook', hook); + + hook.on('error', function(err){ + self.failHook(hook, err); + }); + + hook.run(function(err){ + hook.removeAllListeners('error'); + if (err) return self.failHook(hook, err); + self.emit('hook end', hook); + next(++i); + }); + } + + process.nextTick(function(){ + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err)`. + * + * @param {String} name + * @param {Array} suites + * @param {Function} fn + * @api private + */ + +Runner.prototype.hooks = function(name, suites, fn){ + var self = this + , orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err){ + if (err) { + self.suite = orig; + return fn(err); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookUp = function(name, fn){ + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookDown = function(name, fn){ + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @api private + */ + +Runner.prototype.parents = function(){ + var suite = this.suite + , suites = []; + while (suite = suite.parent) suites.push(suite); + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTest = function(fn){ + var test = this.test + , self = this; + + try { + test.on('error', function(err){ + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke + * the callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTests = function(suite, fn){ + var self = this + , tests = suite.tests + , test; + + function next(err) { + // if we bail after first err + if (self.failures && suite._bail) return fn(); + + // next test + test = tests.shift(); + + // all done + if (!test) return fn(); + + // grep + if (!self._grep.test(test.fullTitle())) return next(); + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(){ + self.currentRunnable = self.test; + self.runTest(function(err){ + test = self.test; + + if (err) { + self.fail(test, err); + self.emit('test end', test); + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the + * callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runSuite = function(suite, fn){ + var self = this + , i = 0; + + debug('run suite %s', suite.fullTitle()); + this.emit('suite', this.suite = suite); + + function next() { + var curr = suite.suites[i++]; + if (!curr) return done(); + self.runSuite(curr, next); + } + + function done() { + self.suite = suite; + self.hook('afterAll', function(){ + self.emit('suite end', suite); + fn(); + }); + } + + this.hook('beforeAll', function(){ + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @param {Error} err + * @api private + */ + +Runner.prototype.uncaught = function(err){ + debug('uncaught exception'); + var runnable = this.currentRunnable; + if ('failed' == runnable.state) return; + runnable.clearTimeout(); + err.uncaught = true; + this.fail(runnable, err); + + // recover from test + if ('test' == runnable.type) { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @param {Function} fn + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.run = function(fn){ + var self = this + , fn = fn || function(){}; + + debug('start'); + + // callback + this.on('end', function(){ + debug('end'); + process.removeListener('uncaughtException', this.uncaught); + fn(self.failures); + }); + + // run suites + this.emit('start'); + this.runSuite(this.suite, function(){ + debug('finished running'); + self.emit('end'); + }); + + // uncaught exception + process.on('uncaughtException', function(err){ + self.uncaught(err); + }); + + return this; +}; diff --git a/node_modules/mocha/lib/suite.js b/node_modules/mocha/lib/suite.js new file mode 100644 index 0000000..257b502 --- /dev/null +++ b/node_modules/mocha/lib/suite.js @@ -0,0 +1,247 @@ + +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , debug = require('debug')('suite') + , utils = require('./utils') + , Hook = require('./hook'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` + * and parent `Suite`. When a suite with the + * same title is already present, that suite + * is returned to provide nicer reporter + * and more flexible meta-testing. + * + * @param {Suite} parent + * @param {String} title + * @return {Suite} + * @api public + */ + +exports.create = function(parent, title){ + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given + * `title` and `ctx`. + * + * @param {String} title + * @param {Context} ctx + * @api private + */ + +function Suite(title, ctx) { + this.title = title; + this.ctx = ctx; + this.suites = []; + this.tests = []; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._bail = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Suite.prototype.__proto__ = EventEmitter.prototype; + +/** + * Return a clone of this `Suite`. + * + * @return {Suite} + * @api private + */ + +Suite.prototype.clone = function(){ + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000; + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @parma {Boolean} bail + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.bail = function(bail){ + if (0 == arguments.length) return this._bail; + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeAll = function(fn){ + var hook = new Hook('"before all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterAll = function(fn){ + var hook = new Hook('"after all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeEach = function(fn){ + var hook = new Hook('"before each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterEach = function(fn){ + var hook = new Hook('"after each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @param {Suite} suite + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addSuite = function(suite){ + suite.parent = this; + suite.timeout(this.timeout()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @param {Test} test + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addTest = function(test){ + test.parent = this; + test.timeout(this.timeout()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Suite.prototype.fullTitle = function(){ + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) return full + ' ' + this.title; + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @return {Number} + * @api public + */ + +Suite.prototype.total = function(){ + return utils.reduce(this.suites, function(sum, suite){ + return sum + suite.total(); + }, 0) + this.tests.length; +}; diff --git a/node_modules/mocha/lib/test.js b/node_modules/mocha/lib/test.js new file mode 100644 index 0000000..64a2f10 --- /dev/null +++ b/node_modules/mocha/lib/test.js @@ -0,0 +1,49 @@ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Test.prototype.__proto__ = Runnable.prototype; + +/** + * Inspect the context void of private properties. + * + * @return {String} + * @api private + */ + +Test.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + return '_' == key[0] + ? undefined + : 'parent' == key + ? '#' + : val; + }, 2); +}; \ No newline at end of file diff --git a/node_modules/mocha/lib/utils.js b/node_modules/mocha/lib/utils.js new file mode 100644 index 0000000..22a942e --- /dev/null +++ b/node_modules/mocha/lib/utils.js @@ -0,0 +1,189 @@ + +/** + * Module dependencies. + */ + +var fs = require('fs') + , path = require('path') + , join = path.join + , debug = require('debug')('watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * 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, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope) { + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function (arr, obj, start) { + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @param {Object} scope + * @api private + */ + +exports.reduce = function(arr, fn, val, scope) { + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn.call(scope, rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.filter = function(arr, fn, scope) { + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn.call(scope, val, i, arr)) + ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ret){ + ret = ret || []; + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ret); + } else if (path.match(/\.(js|coffee)$/)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; \ No newline at end of file diff --git a/node_modules/mocha/mocha.css b/node_modules/mocha/mocha.css new file mode 100644 index 0000000..b49d91d --- /dev/null +++ b/node_modules/mocha/mocha.css @@ -0,0 +1,137 @@ + +body { + font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 60px 50px; +} + +#mocha h1, h2 { + margin: 0; +} + +#mocha h1 { + margin-top: 15px; + font-size: 1em; + font-weight: 200; +} + +#mocha .suite .suite h1 { + margin-top: 0; + font-size: .8em; +} + +#mocha h2 { + font-size: 12px; + font-weight: normal; + cursor: pointer; +} + +#mocha .suite { + margin-left: 15px; +} + +#mocha .test { + margin-left: 15px; +} + +#mocha .test:hover h2::after { + position: relative; + top: 0; + right: -10px; + content: '(view source)'; + font-size: 12px; + font-family: arial; + color: #888; +} + +#mocha .test.pending:hover h2::after { + content: '(pending)'; + font-family: arial; +} + +#mocha .test.pass::before { + content: '✓'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #00c41c; +} + +#mocha .test.pending { + color: #0b97c4; +} + +#mocha .test.pending::before { + content: '◦'; + color: #0b97c4; +} + +#mocha .test.fail { + color: #c00; +} + +#mocha .test.fail pre { + color: black; +} + +#mocha .test.fail::before { + content: '✖'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #c00; +} + +#mocha .test pre.error { + color: #c00; +} + +#mocha .test pre { + display: inline-block; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + border-bottom-color: #ddd; + -webkit-border-radius: 3px; + -webkit-box-shadow: 0 1px 3px #eee; +} + +#error { + color: #c00; + font-size: 1.5 em; + font-weight: 100; + letter-spacing: 1px; +} + +#stats { + position: fixed; + top: 15px; + right: 10px; + font-size: 12px; + margin: 0; + color: #888; +} + +#stats .progress { + float: right; + padding-top: 0; +} + +#stats em { + color: black; +} + +#stats li { + display: inline-block; + margin: 0 5px; + list-style: none; + padding-top: 11px; +} + +code .comment { color: #ddd } +code .init { color: #2F6FAD } +code .string { color: #5890AD } +code .keyword { color: #8A6343 } +code .number { color: #2F6FAD } diff --git a/node_modules/mocha/mocha.js b/node_modules/mocha/mocha.js new file mode 100644 index 0000000..a2fe75d --- /dev/null +++ b/node_modules/mocha/mocha.js @@ -0,0 +1,4067 @@ +;(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.charAt(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("browser/debug.js", function(module, exports, require){ + +module.exports = function(type){ + return function(){ + + } +}; +}); // module: browser/debug.js + +require.register("browser/diff.js", function(module, exports, require){ + +}); // module: browser/diff.js + +require.register("browser/events.js", function(module, exports, require){ + +/** + * Module exports. + */ + +exports.EventEmitter = EventEmitter; + +/** + * Check if `obj` is an array. + */ + +function isArray(obj) { + return '[object Array]' == {}.toString.call(obj); +} + +/** + * 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 (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 (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) { + 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 (!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 = [].slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (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; +}; +}); // module: browser/events.js + +require.register("browser/fs.js", function(module, exports, require){ + +}); // module: browser/fs.js + +require.register("browser/path.js", function(module, exports, require){ + +}); // module: browser/path.js + +require.register("browser/progress.js", function(module, exports, require){ + +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ + +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.size = function(n){ + this._size = n; + return this; +}; + +/** + * Set text to `str`. + * + * @param {String} str + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.text = function(str){ + this._text = str; + return this; +}; + +/** + * Set font size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.fontSize = function(n){ + this._fontSize = n; + return this; +}; + +/** + * Set font `family`. + * + * @param {String} family + * @return {Progress} for chaining + */ + +Progress.prototype.font = function(family){ + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + */ + +Progress.prototype.update = function(n){ + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} for chaining + */ + +Progress.prototype.draw = function(ctx){ + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + + return this; +}; + +}); // module: browser/progress.js + +require.register("browser/tty.js", function(module, exports, require){ + +exports.isatty = function(){ + return true; +}; + +exports.getWindowSize = function(){ + return [window.innerHeight, window.innerWidth]; +}; +}); // module: browser/tty.js + +require.register("context.js", function(module, exports, require){ + +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @api private + */ + +function Context(){} + +/** + * Set the context `Runnable` to `runnable`. + * + * @param {Runnable} runnable + * @return {Context} + * @api private + */ + +Context.prototype.runnable = function(runnable){ + this._runnable = runnable; + return this; +}; + +/** + * Set test timeout `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.timeout = function(ms){ + this._runnable.timeout(ms); + return this; +}; + +/** + * Inspect the context void of `._runnable`. + * + * @return {String} + * @api private + */ + +Context.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + return '_runnable' == key + ? undefined + : val; + }, 2); +}; + +}); // module: context.js + +require.register("hook.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Hook.prototype = new Runnable; +Hook.prototype.constructor = Hook; + + +}); // module: hook.js + +require.register("interfaces/bdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * BDD-style interface: + * + * describe('Array', function(){ + * describe('#indexOf()', function(){ + * it('should return -1 when not present', function(){ + * + * }); + * + * it('should return the index when present', function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + // noop variants + + context.xdescribe = function(){}; + context.xit = function(){}; + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn(); + suites.shift(); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; + +}); // module: interfaces/bdd.js + +require.register("interfaces/exports.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function(){ + * + * }, + * + * 'should return the correct index when the value is present': function(){ + * + * } + * } + * }; + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('require', visit); + + function visit(obj) { + var suite; + for (var key in obj) { + if ('function' == typeof obj[key]) { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + suites[0].addTest(new Test(key, fn)); + } + } else { + var suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key]); + suites.shift(); + } + } + } +}; +}); // module: interfaces/exports.js + +require.register("interfaces/index.js", function(module, exports, require){ + +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); + +}); // module: interfaces/index.js + +require.register("interfaces/qunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function(){ + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function(){ + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function(){ + * ok('foo'.length == 3); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; + +}); // module: interfaces/qunit.js + +require.register("interfaces/tdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * suite('Array', function(){ + * suite('#indexOf()', function(){ + * suiteSetup(function(){ + * + * }); + * + * test('should return -1 when not present', function(){ + * + * }); + * + * test('should return the index when present', function(){ + * + * }); + * + * suiteTeardown(function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context){ + + /** + * Execute before each test case. + */ + + context.setup = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.teardown = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Execute before the suite. + */ + + context.suiteSetup = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after the suite. + */ + + context.suiteTeardown = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.suite = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn(); + suites.shift(); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + suites[0].addTest(new Test(title, fn)); + }; + }); +}; + +}); // module: interfaces/tdd.js + +require.register("mocha.js", function(module, exports, require){ + +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('browser/path'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * Library version. + */ + +exports.version = '1.0.1'; + +/** + * Expose internals. + */ + +exports.utils = require('./utils'); +exports.interfaces = require('./interfaces'); +exports.reporters = require('./reporters'); +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @param {String} name + * @return {String} + * @api private + */ + +function image(name) { + return __dirname + '/../images/' + name + '.png'; +} + +/** + * Setup mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `ignoreLeaks` ignore global leaks + * + * @param {Object} options + * @api public + */ + +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + this.suite = new exports.Suite('', new exports.Context); + this.ui(options.ui); + this.reporter(options.reporter); + if (options.timeout) this.suite.timeout(options.timeout); +} + +/** + * Add test `file`. + * + * @param {String} file + * @api public + */ + +Mocha.prototype.addFile = function(file){ + this.files.push(file); + return this; +}; + +/** + * Set reporter to `name`, defaults to "dot". + * + * @param {String} name + * @api public + */ + +Mocha.prototype.reporter = function(name){ + name = name || 'dot'; + this._reporter = require('./reporters/' + name); + if (!this._reporter) throw new Error('invalid reporter "' + name + '"'); + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @param {String} bdd + * @api public + */ + +Mocha.prototype.ui = function(name){ + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) throw new Error('invalid interface "' + name + '"'); + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ + +Mocha.prototype.loadFiles = function(){ + var suite = this.suite; + this.files.forEach(function(file){ + file = path.resolve(file); + suite.emit('pre-require', global, file); + suite.emit('require', require(file), file); + suite.emit('post-require', global, file); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ + +Mocha.prototype.growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { title: 'Failed', image: image('fail') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + title: 'Passed' + , image: image('pass') + }); + } + }); +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @param {Function} fn + * @return {Runner} + * @api public + */ + +Mocha.prototype.run = function(fn){ + this.loadFiles(); + var suite = this.suite; + var options = this.options; + var runner = new exports.Runner(suite); + var reporter = new this._reporter(runner); + runner.ignoreLeaks = options.ignoreLeaks; + if (options.grep) runner.grep(options.grep); + if (options.globals) runner.globals(options.globals); + if (options.growl) this.growl(runner, reporter); + return runner.run(fn); +}; + +}); // module: mocha.js + +require.register("reporters/base.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var tty = require('browser/tty') + , diff = require('browser/diff'); + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Enable coloring by default. + */ + +exports.useColors = isatty; + +/** + * Default color map. + */ + +exports.colors = { + 'pass': 90 + , 'fail': 31 + , 'bright pass': 92 + , 'bright fail': 91 + , 'bright yellow': 93 + , 'pending': 36 + , 'suite': 0 + , 'error title': 0 + , 'error message': 31 + , 'error stack': 90 + , 'checkmark': 32 + , 'fast': 90 + , 'medium': 33 + , 'slow': 31 + , 'green': 32 + , 'light': 90 + , 'diff gutter': 90 + , 'diff added': 42 + , 'diff removed': 41 +}; + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {String} type + * @param {String} str + * @return {String} + * @api private + */ + +var color = exports.color = function(type, str) { + if (!exports.useColors) return str; + return '\033[' + exports.colors[type] + 'm' + str + '\033[0m'; +}; + +/** + * Expose term window size, with some + * defaults for when stderr is not a tty. + */ + +exports.window = { + width: isatty + ? process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1] + : 75 +}; + +/** + * Expose some basic cursor interactions + * that are common among reporters. + */ + +exports.cursor = { + hide: function(){ + process.stdout.write('\033[?25l'); + }, + + show: function(){ + process.stdout.write('\033[?25h'); + }, + + deleteLine: function(){ + process.stdout.write('\033[2K'); + }, + + beginningOfLine: function(){ + process.stdout.write('\033[0G'); + }, + + CR: function(){ + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } +}; + +/** + * A test is considered slow if it + * exceeds the following value in milliseconds. + */ + +exports.slow = 75; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures){ + console.error(); + failures.forEach(function(test, i){ + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var err = test.err + , message = err.message || '' + , stack = err.stack || message + , index = stack.indexOf(message) + message.length + , msg = stack.slice(0, index) + , actual = err.actual + , expected = err.expected; + + // actual / expected diff + if ('string' == typeof actual && 'string' == typeof expected) { + var len = Math.max(actual.length, expected.length); + + if (len < 20) msg = errorDiff(err, 'Chars'); + else msg = errorDiff(err, 'Words'); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + + fmt = color('error title', ' %s) %s:\n%s') + + color('error stack', '\n%s\n'); + } + + // indent stack trace without msg + stack = stack.slice(index ? index + 1 : index) + .replace(/^/gm, ' '); + + console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var self = this + , stats = this.stats = { suites: 0, tests: 0, passes: 0, failures: 0 } + , failures = this.failures = []; + + if (!runner) return; + this.runner = runner; + + runner.on('start', function(){ + stats.start = new Date; + }); + + runner.on('suite', function(suite){ + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function(test){ + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test){ + stats.passes = stats.passes || 0; + + var medium = exports.slow / 2; + test.speed = test.duration > exports.slow + ? 'slow' + : test.duration > medium + ? 'medium' + : 'fast'; + + stats.passes++; + }); + + runner.on('fail', function(test, err){ + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function(){ + stats.end = new Date; + stats.duration = new Date - stats.start; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ + +Base.prototype.epilogue = function(){ + var stats = this.stats + , fmt; + + console.log(); + + // failure + if (stats.failures) { + fmt = color('bright fail', ' ✖') + + color('fail', ' %d of %d tests failed') + + color('light', ':') + + console.error(fmt, stats.failures, this.runner.total); + Base.list(this.failures); + console.error(); + return; + } + + // pass + fmt = color('bright pass', ' ✔') + + color('green', ' %d tests complete') + + color('light', ' (%dms)'); + + console.log(fmt, stats.tests || 0, stats.duration); + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @param {String} str + * @param {String} len + * @return {String} + * @api private + */ + +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Return a character diff for `err`. + * + * @param {Error} err + * @return {String} + * @api private + */ + +function errorDiff(err, type) { + return diff['diff' + type](err.actual, err.expected).map(function(str){ + if (str.added) return colorLines('diff added', str.value); + if (str.removed) return colorLines('diff removed', str.value); + return str.value; + }).join(''); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @param {String} name + * @param {String} str + * @return {String} + * @api private + */ + +function colorLines(name, str) { + return str.split('\n').map(function(str){ + return color(name, str); + }).join('\n'); +} + +}); // module: reporters/base.js + +require.register("reporters/doc.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Doc(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite){ + if (suite.root) return; + ++indents; + console.log('%s
', indent()); + ++indents; + console.log('%s

%s

', indent(), suite.title); + console.log('%s
', indent()); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + console.log('%s
', indent()); + --indents; + console.log('%s
', indent()); + --indents; + }); + + runner.on('pass', function(test){ + console.log('%s
%s
', indent(), test.title); + var code = utils.escape(clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + }); +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return str; +} +}); // module: reporters/doc.js + +require.register("reporters/dot.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Dot(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , n = 0; + + runner.on('start', function(){ + process.stdout.write('\n '); + }); + + runner.on('pending', function(test){ + process.stdout.write(color('pending', '.')); + }); + + runner.on('pass', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + if ('slow' == test.speed) { + process.stdout.write(color('bright yellow', '.')); + } else { + process.stdout.write(color(test.speed, '.')); + } + }); + + runner.on('fail', function(test, err){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('fail', '.')); + }); + + runner.on('end', function(){ + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Dot.prototype = new Base; +Dot.prototype.constructor = Dot; + +}); // module: reporters/dot.js + +require.register("reporters/html-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov') + , fs = require('browser/fs'); + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTMLCov(runner) { + var jade = require('jade') + , file = __dirname + '/templates/coverage.jade' + , str = fs.readFileSync(file, 'utf8') + , fn = jade.compile(str, { filename: file }) + , self = this; + + JSONCov.call(this, runner, false); + + runner.on('end', function(){ + process.stdout.write(fn({ + cov: self.cov + , coverageClass: coverageClass + })); + }); +} + +function coverageClass(n) { + if (n >= 75) return 'high'; + if (n >= 50) return 'medium'; + if (n >= 25) return 'low'; + return 'terrible'; +} +}); // module: reporters/html-cov.js + +require.register("reporters/html.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , Progress = require('../browser/progress') + , escape = utils.escape; + +/** + * Expose `Doc`. + */ + +exports = module.exports = HTML; + +/** + * Stats template. + */ + +var statsTemplate = '
    ' + + '
  • ' + + '
  • passes: 0
  • ' + + '
  • failures: 0
  • ' + + '
  • duration: 0s
  • ' + + '
'; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTML(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , root = document.getElementById('mocha') + , stat = fragment(statsTemplate) + , items = stat.getElementsByTagName('li') + , passes = items[1].getElementsByTagName('em')[0] + , failures = items[2].getElementsByTagName('em')[0] + , duration = items[3].getElementsByTagName('em')[0] + , canvas = stat.getElementsByTagName('canvas')[0] + , stack = [root] + , progress + , ctx + + if (canvas.getContext) { + ctx = canvas.getContext('2d'); + progress = new Progress; + } + + if (!root) return error('#mocha div missing, add it to your document'); + + root.appendChild(stat); + + if (progress) progress.size(40); + + runner.on('suite', function(suite){ + if (suite.root) return; + + // suite + var el = fragment('

%s

', suite.title); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('div')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + stack.shift(); + }); + + runner.on('fail', function(test, err){ + if ('hook' == test.type || err.uncaught) runner.emit('test end', test); + }); + + runner.on('test end', function(test){ + // TODO: add to stats + var percent = stats.tests / total * 100 | 0; + if (progress) progress.update(percent).draw(ctx); + + // update stats + var ms = new Date - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(duration, (ms / 1000).toFixed(2)); + + // test + if ('passed' == test.state) { + var el = fragment('

%e

', test.title); + } else if (test.pending) { + var el = fragment('

%e

', test.title); + } else { + var el = fragment('

%e

', test.title); + var str = test.err.stack || test.err.toString(); + + // FF / Opera do not add the message + if (!~str.indexOf(test.err.message)) { + str = test.err.message + '\n' + str; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == str) str = test.err.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { + str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; + } + + el.appendChild(fragment('
%e
', str)); + } + + // toggle code + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function(){ + pre.style.display = 'none' == pre.style.display + ? 'block' + : 'none'; + }); + + // code + // TODO: defer + if (!test.pending) { + var pre = fragment('
%e
', clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + stack[0].appendChild(el); + }); +} + +/** + * Display error `msg`. + */ + +function error(msg) { + document.body.appendChild(fragment('
%s
', msg)); +} + +/** + * Return a DOM fragment from `html`. + */ + +function fragment(html) { + var args = arguments + , div = document.createElement('div') + , i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type){ + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + } + }); + + return div.firstChild; +} + +/** + * Set `el` text to `str`. + */ + +function text(el, str) { + if (el.textContent) { + el.textContent = str; + } else { + el.innerText = str; + } +} + +/** + * Listen on `event` with callback `fn`. + */ + +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str + .replace(re, '') + .replace(/^\s+/, ''); + + return str; +} + +}); // module: reporters/html.js + +require.register("reporters/index.js", function(module, exports, require){ + +exports.Base = require('./base'); +exports.Dot = require('./dot'); +exports.Doc = require('./doc'); +exports.TAP = require('./tap'); +exports.JSON = require('./json'); +exports.HTML = require('./html'); +exports.List = require('./list'); +exports.Min = require('./min'); +exports.Spec = require('./spec'); +exports.Progress = require('./progress'); +exports.Landing = require('./landing'); +exports.JSONCov = require('./json-cov'); +exports.HTMLCov = require('./html-cov'); +exports.JSONStream = require('./json-stream'); +exports.XUnit = require('./xunit') +exports.Teamcity = require('./teamcity') + +}); // module: reporters/index.js + +require.register("reporters/json-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @param {Boolean} output + * @api public + */ + +function JSONCov(runner, output) { + var self = this + , output = 1 == arguments.length ? true : output; + + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) return; + process.stdout.write(JSON.stringify(result, null, 2 )); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @param {Object} cov + * @return {Object} + * @api private + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage' + , sloc: 0 + , hits: 0 + , misses: 0 + , coverage: 0 + , files: [] + }; + + for (var filename in cov) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +}; + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @param {String} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + * @api private + */ + +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num){ + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line + , coverage: data[num] === undefined + ? '' + : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} + +}); // module: reporters/json-cov.js + +require.register("reporters/json-stream.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total; + + runner.on('start', function(){ + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test){ + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err){ + console.log(JSON.stringify(['fail', clean(test)])); + }); + + runner.on('end', function(){ + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json-stream.js + +require.register("reporters/json.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @param {Runner} runner + * @api public + */ + +function JSONReporter(runner) { + var self = this; + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var obj = { + stats: self.stats + , tests: tests.map(clean) + , failures: failures.map(clean) + , passes: passes.map(clean) + }; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json.js + +require.register("reporters/landing.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Landing(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , total = runner.total + , stream = process.stdout + , plane = color('plane', '✈') + , crashed = -1 + , n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function(){ + stream.write('\n '); + cursor.hide(); + }); + + runner.on('test end', function(test){ + // check if the plane crashed + var col = -1 == crashed + ? width * ++n / total | 0 + : crashed; + + // show the crash + if ('failed' == test.state) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\033[4F\n\n'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane) + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\033[0m'); + }); + + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Landing.prototype = new Base; +Landing.prototype.constructor = Landing; + +}); // module: reporters/landing.js + +require.register("reporters/list.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 0; + + runner.on('start', function(){ + console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test){ + var fmt = color('checkmark', ' ✓') + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +List.prototype = new Base; +List.prototype.constructor = List; + + +}); // module: reporters/list.js + +require.register("reporters/markdown.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Markdown(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , level = 0 + , buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function indent() { + return Array(level).join(' '); + } + + function mapTOC(suite, obj) { + var ret = obj; + obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + suite.suites.forEach(function(suite){ + mapTOC(suite, obj); + }); + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if ('suite' == key) continue; + if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + if (key) buf += Array(level).join(' ') + link; + buf += stringifyTOC(obj[key], level); + } + --level; + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite){ + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '
' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function(suite){ + --level; + }); + + runner.on('pass', function(test){ + var code = clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function(){ + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +function clean(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , re = new RegExp('^ {' + spaces + '}', 'gm'); + + str = str.replace(re, ''); + + return str; +} +}); // module: reporters/markdown.js + +require.register("reporters/min.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @param {Runner} runner + * @api public + */ + +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function(){ + // clear screen + process.stdout.write('\033[2J'); + // set cursor position + process.stdout.write('\033[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Min.prototype = new Base; +Min.prototype.constructor = Min; + +}); // module: reporters/min.js + +require.register("reporters/progress.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @param {Runner} runner + * @param {Object} options + * @api public + */ + +function Progress(runner, options) { + Base.call(this, runner); + + var self = this + , options = options || {} + , stats = this.stats + , width = Base.window.width * .50 | 0 + , total = runner.total + , complete = 0 + , max = Math.max; + + // default chars + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || '⋅'; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function(){ + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function(){ + var incomplete = total - complete + , percent = complete++ / total + , n = width * percent | 0 + , i = width - n; + + cursor.CR(); + process.stdout.write('\033[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +Progress.prototype = new Base; +Progress.prototype.constructor = Progress; + + +}); // module: reporters/progress.js + +require.register("reporters/spec.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Spec(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , indents = 0 + , n = 0; + + function indent() { + return Array(indents).join(' ') + } + + runner.on('start', function(){ + console.log(); + }); + + runner.on('suite', function(suite){ + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function(suite){ + --indents; + if (1 == indents) console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test){ + if ('fast' == test.speed) { + var fmt = indent() + + color('checkmark', ' ✓') + + color('pass', ' %s '); + cursor.CR(); + console.log(fmt, test.title); + } else { + var fmt = indent() + + color('checkmark', ' ✓') + + color('pass', ' %s ') + + color(test.speed, '(%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +Spec.prototype = new Base; +Spec.prototype.constructor = Spec; + + +}); // module: reporters/spec.js + +require.register("reporters/tap.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @param {Runner} runner + * @api public + */ + +function TAP(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , n = 1; + + runner.on('start', function(){ + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function(){ + ++n; + }); + + runner.on('pending', function(test){ + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test){ + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err){ + console.log('not ok %d %s', n, title(test)); + console.log(err.stack.replace(/^/gm, ' ')); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @param {Object} test + * @return {String} + * @api private + */ + +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +}); // module: reporters/tap.js + +require.register("reporters/teamcity.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Teamcity(runner) { + Base.call(this, runner); + var stats = this.stats; + + runner.on('start', function() { + console.log("##teamcity[testSuiteStarted name='mocha.suite']"); + }); + + runner.on('test', function(test) { + console.log("##teamcity[testStarted name='%s']", escape(test.fullTitle())); + }); + + runner.on('fail', function(test, err) { + console.log("##teamcity[testFailed name='%s' message='%s']", escape(test.fullTitle()), escape(err.message)); + }); + + runner.on('pending', function(test) { + console.log("##teamcity[testIgnored name='%s' message='pending']", escape(test.fullTitle())); + }); + + runner.on('test end', function(test) { + console.log("##teamcity[testFinished name='%s' duration='%s']", escape(test.fullTitle()), test.duration); + }); + + runner.on('end', function() { + console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='%s']", stats.duration); + }); +} + +/** + * Escape the given `str`. + */ + +function escape(str) { + return str.replace(/'/g, "|'"); +} +}); // module: reporters/teamcity.js + +require.register("reporters/xunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , escape = utils.escape; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @param {Runner} runner + * @api public + */ + +function XUnit(runner) { + Base.call(this, runner); + var stats = this.stats + , tests = [] + , self = this; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('end', function(){ + console.log(tag('testsuite', { + name: 'Mocha Tests' + , tests: stats.tests + , failures: stats.failures + , errors: stats.failures + , skip: stats.tests - stats.failures - stats.passes + , timestamp: (new Date).toUTCString() + , time: stats.duration / 1000 + }, false)); + + tests.forEach(test); + console.log(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +XUnit.prototype = new Base; +XUnit.prototype.constructor = XUnit; + + +/** + * Output tag for the given `test.` + */ + +function test(test) { + var attrs = { + classname: test.parent.fullTitle() + , name: test.title + , time: test.duration / 1000 + }; + + if ('failed' == test.state) { + var err = test.err; + attrs.message = escape(err.message); + console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + } else if (test.pending) { + console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + console.log(tag('testcase', attrs, true) ); + } +} + +/** + * HTML tag helper. + */ + +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>' + , pairs = [] + , tag; + + for (var key in attrs) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) tag += content + ''; +} + +}); // module: reporters/xunit.js + +require.register("runnable.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('runnable'); + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = ! this.async; + this._timeout = 2000; + this.timedOut = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runnable.prototype = new EventEmitter; +Runnable.prototype.constructor = Runnable; + + +/** + * Set & get timeout `ms`. + * + * @param {Number} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) this.resetTimeout(); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Runnable.prototype.fullTitle = function(){ + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ + +Runnable.prototype.clearTimeout = function(){ + clearTimeout(this.timer); +}; + +/** + * Reset the timeout. + * + * @api private + */ + +Runnable.prototype.resetTimeout = function(){ + var self = this + , ms = this.timeout(); + + this.clearTimeout(); + if (ms) { + this.timer = setTimeout(function(){ + self.callback(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runnable.prototype.run = function(fn){ + var self = this + , ms = this.timeout() + , start = new Date + , ctx = this.ctx + , finished + , emitted; + + if (ctx) ctx.runnable(this); + + // timeout + if (this.async) { + if (ms) { + this.timer = setTimeout(function(){ + done(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } + } + + // called multiple times + function multiple() { + if (emitted) return; + emitted = true; + self.emit('error', new Error('done() called multiple times')); + } + + // finished + function done(err) { + if (self.timedOut) return; + if (finished) return multiple(); + self.clearTimeout(); + self.duration = new Date - start; + finished = true; + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // async + if (this.async) { + try { + this.fn.call(ctx, function(err){ + if (err instanceof Error) return done(err); + if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + done(); + }); + } catch (err) { + done(err); + } + return; + } + + // sync + try { + if (!this.pending) this.fn.call(ctx); + this.duration = new Date - start; + fn(); + } catch (err) { + fn(err); + } +}; + +}); // module: runnable.js + +require.register("runner.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('runner') + , Test = require('./test') + , utils = require('./utils') + , noop = function(){}; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * + * @api public + */ + +function Runner(suite) { + var self = this; + this._globals = []; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test){ self.checkGlobals(test); }); + this.on('hook end', function(hook){ self.checkGlobals(hook); }); + this.grep(/.*/); + this.globals(utils.keys(global).concat(['errno'])); +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Runner.prototype = new EventEmitter; +Runner.prototype.constructor = Runner; + + +/** + * Run tests with full titles matching `re`. + * + * @param {RegExp} re + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.grep = function(re){ + debug('grep %s', re); + this._grep = re; + return this; +}; + +/** + * Allow the given `arr` of globals. + * + * @param {Array} arr + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.globals = function(arr){ + if (0 == arguments.length) return this._globals; + debug('globals %j', arr); + utils.forEach(arr, function(arr){ + this._globals.push(arr); + }, this); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ + +Runner.prototype.checkGlobals = function(test){ + if (this.ignoreLeaks) return; + + var leaks = utils.filter(utils.keys(global), function(key){ + return !~utils.indexOf(this._globals, key) && (!global.navigator || 'onerror' !== key); + }, this); + + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @param {Test} test + * @param {Error} err + * @api private + */ + +Runner.prototype.fail = function(test, err){ + ++this.failures; + test.state = 'failed'; + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures (currently) hard-end due + * to that fact that a failing hook will + * surely cause subsequent tests to fail, + * causing jumbled reporting. + * + * @param {Hook} hook + * @param {Error} err + * @api private + */ + +Runner.prototype.failHook = function(hook, err){ + this.fail(hook, err); + this.emit('end'); +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @param {String} name + * @param {Function} function + * @api private + */ + +Runner.prototype.hook = function(name, fn){ + var suite = this.suite + , hooks = suite['_' + name] + , ms = suite._timeout + , self = this + , timer; + + function next(i) { + var hook = hooks[i]; + if (!hook) return fn(); + self.currentRunnable = hook; + + self.emit('hook', hook); + + hook.on('error', function(err){ + self.failHook(hook, err); + }); + + hook.run(function(err){ + hook.removeAllListeners('error'); + if (err) return self.failHook(hook, err); + self.emit('hook end', hook); + next(++i); + }); + } + + process.nextTick(function(){ + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err)`. + * + * @param {String} name + * @param {Array} suites + * @param {Function} fn + * @api private + */ + +Runner.prototype.hooks = function(name, suites, fn){ + var self = this + , orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err){ + if (err) { + self.suite = orig; + return fn(err); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookUp = function(name, fn){ + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookDown = function(name, fn){ + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @api private + */ + +Runner.prototype.parents = function(){ + var suite = this.suite + , suites = []; + while (suite = suite.parent) suites.push(suite); + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTest = function(fn){ + var test = this.test + , self = this; + + try { + test.on('error', function(err){ + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke + * the callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTests = function(suite, fn){ + var self = this + , tests = suite.tests + , test; + + function next(err) { + // if we bail after first err + if (self.failures && suite._bail) return fn(); + + // next test + test = tests.shift(); + + // all done + if (!test) return fn(); + + // grep + if (!self._grep.test(test.fullTitle())) return next(); + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(){ + self.currentRunnable = self.test; + self.runTest(function(err){ + test = self.test; + + if (err) { + self.fail(test, err); + self.emit('test end', test); + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the + * callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runSuite = function(suite, fn){ + var self = this + , i = 0; + + debug('run suite %s', suite.fullTitle()); + this.emit('suite', this.suite = suite); + + function next() { + var curr = suite.suites[i++]; + if (!curr) return done(); + self.runSuite(curr, next); + } + + function done() { + self.suite = suite; + self.hook('afterAll', function(){ + self.emit('suite end', suite); + fn(); + }); + } + + this.hook('beforeAll', function(){ + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @param {Error} err + * @api private + */ + +Runner.prototype.uncaught = function(err){ + debug('uncaught exception'); + var runnable = this.currentRunnable; + if ('failed' == runnable.state) return; + runnable.clearTimeout(); + err.uncaught = true; + this.fail(runnable, err); + + // recover from test + if ('test' == runnable.type) { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @param {Function} fn + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.run = function(fn){ + var self = this + , fn = fn || function(){}; + + debug('start'); + + // callback + this.on('end', function(){ + debug('end'); + process.removeListener('uncaughtException', this.uncaught); + fn(self.failures); + }); + + // run suites + this.emit('start'); + this.runSuite(this.suite, function(){ + debug('finished running'); + self.emit('end'); + }); + + // uncaught exception + process.on('uncaughtException', function(err){ + self.uncaught(err); + }); + + return this; +}; + +}); // module: runner.js + +require.register("suite.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('suite') + , utils = require('./utils') + , Hook = require('./hook'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` + * and parent `Suite`. When a suite with the + * same title is already present, that suite + * is returned to provide nicer reporter + * and more flexible meta-testing. + * + * @param {Suite} parent + * @param {String} title + * @return {Suite} + * @api public + */ + +exports.create = function(parent, title){ + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given + * `title` and `ctx`. + * + * @param {String} title + * @param {Context} ctx + * @api private + */ + +function Suite(title, ctx) { + this.title = title; + this.ctx = ctx; + this.suites = []; + this.tests = []; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._bail = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Suite.prototype = new EventEmitter; +Suite.prototype.constructor = Suite; + + +/** + * Return a clone of this `Suite`. + * + * @return {Suite} + * @api private + */ + +Suite.prototype.clone = function(){ + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000; + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @parma {Boolean} bail + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.bail = function(bail){ + if (0 == arguments.length) return this._bail; + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeAll = function(fn){ + var hook = new Hook('"before all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterAll = function(fn){ + var hook = new Hook('"after all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeEach = function(fn){ + var hook = new Hook('"before each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterEach = function(fn){ + var hook = new Hook('"after each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @param {Suite} suite + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addSuite = function(suite){ + suite.parent = this; + suite.timeout(this.timeout()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @param {Test} test + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addTest = function(test){ + test.parent = this; + test.timeout(this.timeout()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Suite.prototype.fullTitle = function(){ + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) return full + ' ' + this.title; + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @return {Number} + * @api public + */ + +Suite.prototype.total = function(){ + return utils.reduce(this.suites, function(sum, suite){ + return sum + suite.total(); + }, 0) + this.tests.length; +}; + +}); // module: suite.js + +require.register("test.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +Test.prototype = new Runnable; +Test.prototype.constructor = Test; + + +/** + * Inspect the context void of private properties. + * + * @return {String} + * @api private + */ + +Test.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + return '_' == key[0] + ? undefined + : 'parent' == key + ? '#' + : val; + }, 2); +}; +}); // module: test.js + +require.register("utils.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var fs = require('browser/fs') + , path = require('browser/path') + , join = path.join + , debug = require('browser/debug')('watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * 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, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope) { + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function (arr, obj, start) { + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @param {Object} scope + * @api private + */ + +exports.reduce = function(arr, fn, val, scope) { + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn.call(scope, rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.filter = function(arr, fn, scope) { + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn.call(scope, val, i, arr)) + ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ret){ + ret = ret || []; + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ret); + } else if (path.match(/\.(js|coffee)$/)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; +}); // module: utils.js +/** + * Node shims. + * + * These are meant only to allow + * mocha.js to run untouched, not + * to allow running node code in + * the browser. + */ + +process = {}; +process.exit = function(status){}; +process.stdout = {}; +global = window; + +/** + * next tick implementation. + */ + +process.nextTick = (function(){ + // postMessage behaves badly on IE8 + if (window.ActiveXObject || !window.postMessage) { + return function(fn){ fn() }; + } + + // based on setZeroTimeout by David Baron + // - http://dbaron.org/log/20100309-faster-timeouts + var timeouts = [] + , name = 'mocha-zero-timeout' + + return function(fn){ + timeouts.push(fn); + window.postMessage(name, '*'); + window.addEventListener('message', function(e){ + if (e.source == window && e.data == name) { + if (e.stopPropagation) e.stopPropagation(); + if (timeouts.length) timeouts.shift()(); + } + }, true); + } +})(); + +/** + * Remove uncaughtException listener. + */ + +process.removeListener = function(e){ + if ('uncaughtException' == e) { + window.onerror = null; + } +}; + +/** + * Implements uncaughtException listener. + */ + +process.on = function(e, fn){ + if ('uncaughtException' == e) { + window.onerror = fn; + } +}; + +/** + * Expose mocha. + */ + +window.mocha = require('mocha'); + +// boot +;(function(){ + var suite = new mocha.Suite('', new mocha.Context) + , utils = mocha.utils + , options = {} + + /** + * Highlight the given string of `js`. + */ + + function highlight(js) { + return js + .replace(//g, '>') + .replace(/\/\/(.*)/gm, '//$1') + .replace(/('.*?')/gm, '$1') + .replace(/(\d+\.\d+)/gm, '$1') + .replace(/(\d+)/gm, '$1') + .replace(/\bnew *(\w+)/gm, 'new $1') + .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') + } + + /** + * Highlight code contents. + */ + + function highlightCode() { + var code = document.getElementsByTagName('code'); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } + } + + /** + * Parse the given `qs`. + */ + + function parse(qs) { + return utils.reduce(qs.replace('?', '').split('&'), function(obj, pair){ + var i = pair.indexOf('=') + , key = pair.slice(0, i) + , val = pair.slice(++i); + + obj[key] = decodeURIComponent(val); + return obj; + }, {}); + } + + /** + * Setup mocha with the given setting options. + */ + + mocha.setup = function(opts){ + if ('string' === typeof opts) options.ui = opts; + else options = opts; + + ui = mocha.interfaces[options.ui]; + if (!ui) throw new Error('invalid mocha interface "' + ui + '"'); + if (options.timeout) suite.timeout(options.timeout); + ui(suite); + suite.emit('pre-require', window); + }; + + /** + * Run mocha, returning the Runner. + */ + + mocha.run = function(fn){ + suite.emit('run'); + var runner = new mocha.Runner(suite); + var Reporter = options.reporter || mocha.reporters.HTML; + var reporter = new Reporter(runner); + var query = parse(window.location.search || ""); + if (query.grep) runner.grep(new RegExp(query.grep)); + if (options.ignoreLeaks) runner.ignoreLeaks = true; + if (options.globals) runner.globals(options.globals); + runner.globals(['location']); + runner.on('end', highlightCode); + return runner.run(fn); + }; +})(); +})(); \ No newline at end of file diff --git a/node_modules/mocha/node_modules/.bin/jade b/node_modules/mocha/node_modules/.bin/jade new file mode 100755 index 0000000..fd4122c --- /dev/null +++ b/node_modules/mocha/node_modules/.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/mocha/node_modules/commander/.npmignore b/node_modules/mocha/node_modules/commander/.npmignore new file mode 100644 index 0000000..f1250e5 --- /dev/null +++ b/node_modules/mocha/node_modules/commander/.npmignore @@ -0,0 +1,4 @@ +support +test +examples +*.sock diff --git a/node_modules/mocha/node_modules/commander/.travis.yml b/node_modules/mocha/node_modules/commander/.travis.yml new file mode 100644 index 0000000..f1d0f13 --- /dev/null +++ b/node_modules/mocha/node_modules/commander/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.4 + - 0.6 diff --git a/node_modules/mocha/node_modules/commander/History.md b/node_modules/mocha/node_modules/commander/History.md new file mode 100644 index 0000000..66c6894 --- /dev/null +++ b/node_modules/mocha/node_modules/commander/History.md @@ -0,0 +1,99 @@ + +0.5.2 / 2012-01-17 +================== + + * Added support for 0.7.x + +0.5.1 / 2011-12-20 +================== + + * Fixed `password()` for recent nodes. Closes #36 + +0.5.0 / 2011-12-04 +================== + + * Added sub-command option support [itay] + +0.4.3 / 2011-12-04 +================== + + * Fixed custom help ordering. Closes #32 + +0.4.2 / 2011-11-24 +================== + + * Added travis support + * Fixed: line-buffered input automatically trimmed. Closes #31 + +0.4.1 / 2011-11-18 +================== + + * Removed listening for "close" on --help + +0.4.0 / 2011-11-15 +================== + + * Added support for `--`. Closes #24 + +0.3.3 / 2011-11-14 +================== + + * Fixed: wait for close event when writing help info [Jerry Hamlet] + +0.3.2 / 2011-11-01 +================== + + * Fixed long flag definitions with values [felixge] + +0.3.1 / 2011-10-31 +================== + + * Changed `--version` short flag to `-V` from `-v` + * Changed `.version()` so it's configurable [felixge] + +0.3.0 / 2011-10-31 +================== + + * Added support for long flags only. Closes #18 + +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/mocha/node_modules/commander/Makefile b/node_modules/mocha/node_modules/commander/Makefile new file mode 100644 index 0000000..0074625 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/commander/Readme.md b/node_modules/mocha/node_modules/commander/Readme.md new file mode 100644 index 0000000..2efbe7a --- /dev/null +++ b/node_modules/mocha/node_modules/commander/Readme.md @@ -0,0 +1,263 @@ + +# Commander.js + + The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander). + + [![Build Status](https://secure.travis-ci.org/visionmedia/commander.js.png)](http://travis-ci.org/visionmedia/commander.js) + +## 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') + .usage('[options] ') + .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/mocha/node_modules/commander/index.js b/node_modules/mocha/node_modules/commander/index.js new file mode 100644 index 0000000..06ec1e4 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/commander/lib/commander.js b/node_modules/mocha/node_modules/commander/lib/commander.js new file mode 100644 index 0000000..b9cbd5d --- /dev/null +++ b/node_modules/mocha/node_modules/commander/lib/commander.js @@ -0,0 +1,992 @@ + +/*! + * 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(/[ ,|]+/); + if (flags.length > 1 && !/^[[<]/.test(flags[1])) 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, unknown){ + // Parse any so-far unknown options + unknown = unknown || []; + var parsed = self.parseOptions(unknown); + + // Output help if necessary + outputHelpIfNecessary(self, parsed.unknown); + + // If there are still any unknown options, then we simply + // die, unless someone asked for help, in which case we give it + // to them, and then we die. + if (parsed.unknown.length > 0) { + self.unknownOption(parsed.unknown[0]); + } + + self.args.forEach(function(arg, i){ + if (arg.required && null == args[i]) { + self.missingArgument(arg.name); + } + }); + + // Always append ourselves to the end of the arguments, + // to make sure we match the number of arguments the user + // expects + if (self.args.length) { + args[self.args.length] = self; + } else { + args.push(self); + } + + 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 + var parsed = this.parseOptions(this.normalize(argv.slice(2))); + this.args = parsed.args; + return this.parseArgs(this.args, parsed.unknown); +}; + +/** + * 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, unknown){ + var cmds = this.commands + , len = cmds.length + , name; + + if (args.length) { + name = args[0]; + if (this.listeners(name).length) { + this.emit(args.shift(), args, unknown); + } else { + this.emit('*', args); + } + } else { + outputHelpIfNecessary(this, unknown); + + // If there were no args and we have unknown options, + // then they are extraneous and we need to error. + if (unknown.length > 0) { + this.unknownOption(unknown[0]); + } + } + + 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 = [] + , len = argv.length + , literal + , option + , arg; + + var unknownOptions = []; + + // parse options + for (var i = 0; i < len; ++i) { + arg = argv[i]; + + // literal args after -- + if ('--' == arg) { + literal = true; + continue; + } + + if (literal) { + args.push(arg); + continue; + } + + // find matching Option + 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]) { + unknownOptions.push(arg); + + // If the next argument looks like it might be + // an argument for this option, we pass it on. + // If it isn't, then it'll simply be ignored + if (argv[i+1] && '-' != argv[i+1][0]) { + unknownOptions.push(argv[++i]); + } + continue; + } + + // arg + args.push(arg); + } + + return { args: args, unknown: unknownOptions }; +}; + +/** + * 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 + * @param {String} flags + * @return {Command} for chaining + * @api public + */ + +Command.prototype.version = function(str, flags){ + if (0 == arguments.length) return this._version; + this._version = str; + flags = flags || '-V, --version'; + this.option(flags, '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 args = this.args.map(function(arg){ + return arg.required + ? '<' + arg.name + '>' + : '[' + arg.name + ']'; + }); + + var usage = '[options' + + (this.commands.length ? '] [command' : '') + + ']' + + (this.args.length ? ' ' + args : ''); + 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(); + + // Prepend the help information + return [pad('-h, --help', width) + ' ' + 'output usage information'] + .concat(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 + + (cmd.options.length + ? ' [options]' + : '') + ' ' + 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){ + var self = this; + this.promptSingleLine(str, function parseNumber(val){ + val = Number(val); + if (isNaN(val)) return self.promptSingleLine(str + '(must be a number) ', parseNumber); + fn(val); + }); +}; + +/** + * Prompt for a `Date`. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptForDate = function(str, fn){ + var self = this; + this.promptSingleLine(str, function parseDate(val){ + val = new Date(val); + if (isNaN(val.getTime())) return self.promptSingleLine(str + '(must be a date) ', parseDate); + 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.trim()); + }).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 || '\r\n' == val) { + process.stdin.removeAllListeners('data'); + fn(buf.join('\n')); + } else { + buf.push(val.trimRight()); + } + }).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 = ''; + } + + process.stdin.resume(); + 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(' '); +} + +/** + * Output help information if necessary + * + * @param {Command} command to output help for + * @param {Array} array of options to search for -h or --help + * @api private + */ + +function outputHelpIfNecessary(cmd, options) { + options = options || []; + for (var i = 0; i < options.length; i++) { + if (options[i] == '--help' || options[i] == '-h') { + process.stdout.write(cmd.helpInformation()); + cmd.emit('--help'); + process.exit(0); + } + } +} diff --git a/node_modules/mocha/node_modules/commander/package.json b/node_modules/mocha/node_modules/commander/package.json new file mode 100644 index 0000000..b200164 --- /dev/null +++ b/node_modules/mocha/node_modules/commander/package.json @@ -0,0 +1,38 @@ +{ + "name": "commander", + "version": "0.5.2", + "description": "the complete solution for node.js command-line programs", + "keywords": [ + "command", + "option", + "parser", + "prompt", + "stdin" + ], + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/commander.js.git" + }, + "dependencies": {}, + "devDependencies": { + "should": ">= 0.0.1" + }, + "scripts": { + "test": "make test" + }, + "main": "index", + "engines": { + "node": ">= 0.4.x < 0.8.0" + }, + "_id": "commander@0.5.2", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "commander@0.5.x" +} diff --git a/node_modules/mocha/node_modules/debug/.npmignore b/node_modules/mocha/node_modules/debug/.npmignore new file mode 100644 index 0000000..f1250e5 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/.npmignore @@ -0,0 +1,4 @@ +support +test +examples +*.sock diff --git a/node_modules/mocha/node_modules/debug/History.md b/node_modules/mocha/node_modules/debug/History.md new file mode 100644 index 0000000..f3c1a98 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/History.md @@ -0,0 +1,41 @@ + +0.6.0 / 2012-03-16 +================== + + * Added support for "-" prefix in DEBUG [Vinay Pulim] + * Added `.enabled` flag to the node version [TooTallNate] + +0.5.0 / 2012-02-02 +================== + + * Added: humanize diffs. Closes #8 + * Added `debug.disable()` to the CS variant + * Removed padding. Closes #10 + * Fixed: persist client-side variant again. Closes #9 + +0.4.0 / 2012-02-01 +================== + + * Added browser variant support for older browsers [TooTallNate] + * Added `debug.enable('project:*')` to browser variant [TooTallNate] + * Added padding to diff (moved it to the right) + +0.3.0 / 2012-01-26 +================== + + * Added millisecond diff when isatty, otherwise UTC string + +0.2.0 / 2012-01-22 +================== + + * Added wildcard support + +0.1.0 / 2011-12-02 +================== + + * Added: remove colors unless stderr isatty [TooTallNate] + +0.0.1 / 2010-01-03 +================== + + * Initial release diff --git a/node_modules/mocha/node_modules/debug/Makefile b/node_modules/mocha/node_modules/debug/Makefile new file mode 100644 index 0000000..36a3ed7 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/Makefile @@ -0,0 +1,5 @@ + +test: + @echo "populate me" + +.PHONY: test \ No newline at end of file diff --git a/node_modules/mocha/node_modules/debug/Readme.md b/node_modules/mocha/node_modules/debug/Readme.md new file mode 100644 index 0000000..419fcdf --- /dev/null +++ b/node_modules/mocha/node_modules/debug/Readme.md @@ -0,0 +1,130 @@ + +# debug + + tiny node.js debugging utility. + +## Installation + +``` +$ npm install debug +``` + +## Example + + This module is modelled after node core's debugging technique, allowing you to enable one or more topic-specific debugging functions, for example core does the following within many modules: + +```js +var debug; +if (process.env.NODE_DEBUG && /cluster/.test(process.env.NODE_DEBUG)) { + debug = function(x) { + var prefix = process.pid + ',' + + (process.env.NODE_WORKER_ID ? 'Worker' : 'Master'); + console.error(prefix, x); + }; +} else { + debug = function() { }; +} +``` + + This concept is extremely simple but it works well. With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility. + +Example _app.js_: + +```js +var debug = require('debug')('http') + , http = require('http') + , name = 'My App'; + +// fake app + +debug('booting %s', name); + +http.createServer(function(req, res){ + debug(req.method + ' ' + req.url); + res.end('hello\n'); +}).listen(3000, function(){ + debug('listening'); +}); + +// fake worker of some kind + +require('./worker'); +``` + +Example _worker.js_: + +```js +var debug = require('debug')('worker'); + +setInterval(function(){ + debug('doing some work'); +}, 1000); +``` + + The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples: + + ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png) + + ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png) + +## Millisecond diff + + When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls. + + ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png) + + When stdout is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below: + + ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png) + +## Conventions + + If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". + +## Wildcards + + The "*" character may be used as a wildcard. Suppose for example your library has debuggers named "connect:bodyParser", "connect:compress", "connect:session", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`. + + You can also exclude specific debuggers by prefixing them with a "-" character. For example, `DEBUG=* -connect:*` would include all debuggers except those starting with "connect:". + +## Browser support + + Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. + +```js +a = debug('worker:a'); +b = debug('worker:b'); + +setInterval(function(){ + a('doing some work'); +}, 1000); + +setInterval(function(){ + a('doing some work'); +}, 1200); +``` + +## 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/mocha/node_modules/debug/debug.js b/node_modules/mocha/node_modules/debug/debug.js new file mode 100644 index 0000000..b2f0798 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/debug.js @@ -0,0 +1,122 @@ + +/*! + * debug + * Copyright(c) 2012 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Create a debugger with the given `name`. + * + * @param {String} name + * @return {Type} + * @api public + */ + +function debug(name) { + if (!debug.enabled(name)) return function(){}; + + return function(fmt){ + var curr = new Date; + var ms = curr - (debug[name] || curr); + debug[name] = curr; + + fmt = name + + ' ' + + fmt + + ' +' + debug.humanize(ms); + + // This hackery is required for IE8 + // where `console.log` doesn't have 'apply' + window.console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); + } +} + +/** + * The currently active debug mode names. + */ + +debug.names = []; +debug.skips = []; + +/** + * Enables a debug mode by name. This can include modes + * separated by a colon and wildcards. + * + * @param {String} name + * @api public + */ + +debug.enable = function(name) { + localStorage.debug = name; + + var split = (name || '').split(/[\s,]+/) + , len = split.length; + + for (var i = 0; i < len; i++) { + name = split[i].replace('*', '.*?'); + if (name[0] === '-') { + debug.skips.push(new RegExp('^' + name.substr(1) + '$')); + } + else { + debug.names.push(new RegExp('^' + name + '$')); + } + } +}; + +/** + * Disable debug output. + * + * @api public + */ + +debug.disable = function(){ + debug.enable(''); +}; + +/** + * Humanize the given `ms`. + * + * @param {Number} m + * @return {String} + * @api private + */ + +debug.humanize = function(ms) { + var sec = 1000 + , min = 60 * 1000 + , hour = 60 * min; + + if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; + if (ms >= min) return (ms / min).toFixed(1) + 'm'; + if (ms >= sec) return (ms / sec | 0) + 's'; + return ms + 'ms'; +}; + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +debug.enabled = function(name) { + for (var i = 0, len = debug.skips.length; i < len; i++) { + if (debug.skips[i].test(name)) { + return false; + } + } + for (var i = 0, len = debug.names.length; i < len; i++) { + if (debug.names[i].test(name)) { + return true; + } + } + return false; +}; + +// persist + +if (window.localStorage) debug.enable(localStorage.debug); \ No newline at end of file diff --git a/node_modules/mocha/node_modules/debug/example/app.js b/node_modules/mocha/node_modules/debug/example/app.js new file mode 100644 index 0000000..05374d9 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/example/app.js @@ -0,0 +1,19 @@ + +var debug = require('../')('http') + , http = require('http') + , name = 'My App'; + +// fake app + +debug('booting %s', name); + +http.createServer(function(req, res){ + debug(req.method + ' ' + req.url); + res.end('hello\n'); +}).listen(3000, function(){ + debug('listening'); +}); + +// fake worker of some kind + +require('./worker'); \ No newline at end of file diff --git a/node_modules/mocha/node_modules/debug/example/browser.html b/node_modules/mocha/node_modules/debug/example/browser.html new file mode 100644 index 0000000..7510eee --- /dev/null +++ b/node_modules/mocha/node_modules/debug/example/browser.html @@ -0,0 +1,24 @@ + + + debug() + + + + + + + diff --git a/node_modules/mocha/node_modules/debug/example/wildcards.js b/node_modules/mocha/node_modules/debug/example/wildcards.js new file mode 100644 index 0000000..1fdac20 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/example/wildcards.js @@ -0,0 +1,10 @@ + +var debug = { + foo: require('../')('test:foo'), + bar: require('../')('test:bar'), + baz: require('../')('test:baz') +}; + +debug.foo('foo') +debug.bar('bar') +debug.baz('baz') \ No newline at end of file diff --git a/node_modules/mocha/node_modules/debug/example/worker.js b/node_modules/mocha/node_modules/debug/example/worker.js new file mode 100644 index 0000000..7f6d288 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/example/worker.js @@ -0,0 +1,22 @@ + +// DEBUG=* node example/worker +// DEBUG=worker:* node example/worker +// DEBUG=worker:a node example/worker +// DEBUG=worker:b node example/worker + +var a = require('../')('worker:a') + , b = require('../')('worker:b'); + +function work() { + a('doing lots of uninteresting work'); + setTimeout(work, Math.random() * 1000); +} + +work(); + +function workb() { + b('doing some work'); + setTimeout(workb, Math.random() * 2000); +} + +workb(); \ No newline at end of file diff --git a/node_modules/mocha/node_modules/debug/index.js b/node_modules/mocha/node_modules/debug/index.js new file mode 100644 index 0000000..ee54454 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/debug'); \ No newline at end of file diff --git a/node_modules/mocha/node_modules/debug/lib/debug.js b/node_modules/mocha/node_modules/debug/lib/debug.js new file mode 100644 index 0000000..984dfb5 --- /dev/null +++ b/node_modules/mocha/node_modules/debug/lib/debug.js @@ -0,0 +1,147 @@ + +/*! + * debug + * Copyright(c) 2012 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var tty = require('tty'); + +/** + * Expose `debug()` as the module. + */ + +module.exports = debug; + +/** + * Library version. + */ + +exports.version = '0.6.0'; + +/** + * Enabled debuggers. + */ + +var names = [] + , skips = []; + +(process.env.DEBUG || '') + .split(/[\s,]+/) + .forEach(function(name){ + name = name.replace('*', '.*?'); + if (name[0] === '-') { + skips.push(new RegExp('^' + name.substr(1) + '$')); + } else { + names.push(new RegExp('^' + name + '$')); + } + }); + +/** + * Colors. + */ + +var colors = [6, 2, 3, 4, 5, 1]; + +/** + * Previous debug() call. + */ + +var prev = {}; + +/** + * Previously assigned color. + */ + +var prevColor = 0; + +/** + * Is stdout a TTY? Colored output is disabled when `true`. + */ + +var isatty = tty.isatty(2); + +/** + * Select a color. + * + * @return {Number} + * @api private + */ + +function color() { + return colors[prevColor++ % colors.length]; +} + +/** + * Humanize the given `ms`. + * + * @param {Number} m + * @return {String} + * @api private + */ + +function humanize(ms) { + var sec = 1000 + , min = 60 * 1000 + , hour = 60 * min; + + if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; + if (ms >= min) return (ms / min).toFixed(1) + 'm'; + if (ms >= sec) return (ms / sec | 0) + 's'; + return ms + 'ms'; +} + +/** + * Create a debugger with the given `name`. + * + * @param {String} name + * @return {Type} + * @api public + */ + +function debug(name) { + function disabled(){} + disabled.enabled = false; + + var match = skips.some(function(re){ + return re.test(name); + }); + + if (match) return disabled; + + match = names.some(function(re){ + return re.test(name); + }); + + if (!match) return disabled; + var c = color(); + + function colored(fmt) { + var curr = new Date; + var ms = curr - (prev[name] || curr); + prev[name] = curr; + + fmt = ' \033[9' + c + 'm' + name + ' ' + + '\033[3' + c + 'm\033[90m' + + fmt + '\033[3' + c + 'm' + + ' +' + humanize(ms) + '\033[0m'; + + console.error.apply(this, arguments); + } + + function plain(fmt) { + fmt = new Date().toUTCString() + + ' ' + name + ' ' + fmt; + console.error.apply(this, arguments); + } + + colored.enabled = plain.enabled = true; + + return isatty + ? colored + : plain; +} diff --git a/node_modules/mocha/node_modules/debug/package.json b/node_modules/mocha/node_modules/debug/package.json new file mode 100644 index 0000000..83c12bb --- /dev/null +++ b/node_modules/mocha/node_modules/debug/package.json @@ -0,0 +1,29 @@ +{ + "name": "debug", + "version": "0.6.0", + "description": "small debugging utility", + "keywords": [ + "debug", + "log", + "debugger" + ], + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "dependencies": {}, + "devDependencies": { + "mocha": "*" + }, + "main": "index", + "engines": { + "node": "*" + }, + "_id": "debug@0.6.0", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "debug@*" +} diff --git a/node_modules/mocha/node_modules/diff/LICENSE b/node_modules/mocha/node_modules/diff/LICENSE new file mode 100644 index 0000000..c135dcf --- /dev/null +++ b/node_modules/mocha/node_modules/diff/LICENSE @@ -0,0 +1,31 @@ +Software License Agreement (BSD License) + +Copyright (c) 2009-2011, Kevin Decker + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/node_modules/mocha/node_modules/diff/README.md b/node_modules/mocha/node_modules/diff/README.md new file mode 100644 index 0000000..2ed7ee1 --- /dev/null +++ b/node_modules/mocha/node_modules/diff/README.md @@ -0,0 +1,94 @@ +# jsdiff + +A javascript text differencing implementation. + +Based on the algorithm proposed in +["An O(ND) Difference Algorithm and its Variations" (Myers, 1986)](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927). + +## Installation + + npm install diff + +or + + git clone git://github.com/kpdecker/jsdiff.git + +## API + +* JsDiff.diffChars(oldStr, newStr) + Diffs two blocks of text, comparing character by character. + + Returns a list of change objects (See below). + +* JsDiff.diffWords(oldStr, newStr) + Diffs two blocks of text, comparing word by word. + + Returns a list of change objects (See below). + +* JsDiff.diffLines(oldStr, newStr) + Diffs two blocks of text, comparing line by line. + + Returns a list of change objects (See below). + +* JsDiff.diffCss(oldStr, newStr) + Diffs two blocks of text, comparing CSS tokens. + + Returns a list of change objects (See below). + +* JsDiff.createPatch(fileName, oldStr, newStr, oldHeader, newHeader) + Creates a unified diff patch. + + Parameters: + * fileName : String to be output in the filename sections of the patch + * oldStr : Original string value + * newStr : New string value + * oldHeader : Additional information to include in the old file header + * newHeader : Additional information to include in thew new file header + +* convertChangesToXML(changes) + Converts a list of changes to a serialized XML format + +### Change Objects +Many of the methods above return change objects. These objects are consist of the following fields: + +* value: Text content +* added: True if the value was inserted into the new string +* removed: True of the value was removed from the old string + +Note that some cases may omit a particular flag field. Comparison on the flag fields should always be done in a truthy or falsy manner. + +## [Example](http://kpdecker.github.com/jsdiff) + +## License + +Software License Agreement (BSD License) + +Copyright (c) 2009-2011, Kevin Decker kpdecker@gmail.com + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/node_modules/mocha/node_modules/diff/diff.js b/node_modules/mocha/node_modules/diff/diff.js new file mode 100644 index 0000000..6b7ce24 --- /dev/null +++ b/node_modules/mocha/node_modules/diff/diff.js @@ -0,0 +1,287 @@ +/* See license.txt for terms of usage */ + +/* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ +var JsDiff = (function() { + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + } + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, "&"); + n = n.replace(//g, ">"); + n = n.replace(/"/g, """); + + return n; + } + + + var fbDiff = function(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + }; + fbDiff.prototype = { + diff: function(oldString, newString) { + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString == oldString) { + return [{ value: newString }]; + } + if (!newString) { + return [{ value: oldString, removed: true }]; + } + if (!oldString) { + return [{ value: newString, added: true }]; + } + + newString = this.tokenize(newString); + oldString = this.tokenize(oldString); + + var newLen = newString.length, oldLen = oldString.length; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0 + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { + return bestPath[0].components; + } + + for (var editLength = 1; editLength <= maxEditLength; editLength++) { + for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { + var basePath; + var addPath = bestPath[diagonalPath-1], + removePath = bestPath[diagonalPath+1]; + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath-1] = undefined; + } + + var canAdd = addPath && addPath.newPos+1 < newLen; + var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; + if (!canAdd && !canRemove) { + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { + basePath = clonePath(removePath); + this.pushComponent(basePath.components, oldString[oldPos], undefined, true); + } else { + basePath = clonePath(addPath); + basePath.newPos++; + this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); + } + + var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); + + if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { + return basePath.components; + } else { + bestPath[diagonalPath] = basePath; + } + } + } + }, + + pushComponent: function(components, value, added, removed) { + var last = components[components.length-1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length-1] = + {value: this.join(last.value, value), added: added, removed: removed }; + } else { + components.push({value: value, added: added, removed: removed }); + } + }, + extractCommon: function(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath; + while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { + newPos++; + oldPos++; + + this.pushComponent(basePath.components, newString[newPos], undefined, undefined); + } + basePath.newPos = newPos; + return oldPos; + }, + + equals: function(left, right) { + var reWhitespace = /\S/; + if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { + return true; + } else { + return left == right; + } + }, + join: function(left, right) { + return left + right; + }, + tokenize: function(value) { + return value; + } + }; + + var CharDiff = new fbDiff(); + + var WordDiff = new fbDiff(true); + WordDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\s+|\b)/)); + }; + + var CssDiff = new fbDiff(true); + CssDiff.tokenize = function(value) { + return removeEmpty(value.split(/([{}:;,]|\s+)/)); + }; + + var LineDiff = new fbDiff(); + LineDiff.tokenize = function(value) { + return value.split(/^/m); + }; + + return { + diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, + diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, + diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, + + diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, + + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + var ret = []; + + ret.push("Index: " + fileName); + ret.push("==================================================================="); + ret.push("--- " + fileName + (typeof oldHeader === "undefined" ? "" : "\t" + oldHeader)); + ret.push("+++ " + fileName + (typeof newHeader === "undefined" ? "" : "\t" + newHeader)); + + var diff = LineDiff.diff(oldStr, newStr); + if (!diff[diff.length-1].value) { + diff.pop(); // Remove trailing newline add + } + diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return lines.map(function(entry) { return ' ' + entry; }); + } + function eofNL(curRange, i, current) { + var last = diff[diff.length-2], + isLast = i === diff.length-2, + isLastOfType = i === diff.length-3 && (current.added === !last.added || current.removed === !last.removed); + + // Figure out if this is the last line for the given file and missing NL + if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { + curRange.push('\\ No newline at end of file'); + } + } + + var oldRangeStart = 0, newRangeStart = 0, curRange = [], + oldLine = 1, newLine = 1; + for (var i = 0; i < diff.length; i++) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, "").split("\n"); + current.lines = lines; + + if (current.added || current.removed) { + if (!oldRangeStart) { + var prev = diff[i-1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = contextLines(prev.lines.slice(-4)); + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; })); + eofNL(curRange, i, current); + + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= 8 && i < diff.length-2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, 4); + ret.push( + "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize) + + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize) + + " @@"); + ret.push.apply(ret, curRange); + ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); + if (lines.length <= 4) { + eofNL(ret, i, current); + } + + oldRangeStart = 0; newRangeStart = 0; curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + } + + return ret.join('\n') + '\n'; + }, + + convertChangesToXML: function(changes){ + var ret = []; + for ( var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(""); + } else if (change.removed) { + ret.push(""); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(""); + } else if (change.removed) { + ret.push(""); + } + } + return ret.join(""); + } + }; +})(); + +if (typeof module !== "undefined") { + module.exports = JsDiff; +} diff --git a/node_modules/mocha/node_modules/diff/index.html b/node_modules/mocha/node_modules/diff/index.html new file mode 100644 index 0000000..ecb22fa --- /dev/null +++ b/node_modules/mocha/node_modules/diff/index.html @@ -0,0 +1,89 @@ + + + + + Diff + + + + +
+

Diff

+ + + +
+ +
github.com/kpdecker/jsdiff + + + + + + + +
restaurantaura
+ + + + + diff --git a/node_modules/mocha/node_modules/diff/package.json b/node_modules/mocha/node_modules/diff/package.json new file mode 100644 index 0000000..8786bed --- /dev/null +++ b/node_modules/mocha/node_modules/diff/package.json @@ -0,0 +1,46 @@ +{ + "name": "diff", + "version": "1.0.2", + "description": "A javascript text diff implementation.", + "keywords": [ + "diff", + "javascript" + ], + "maintainers": [ + { + "name": "Kevin Decker", + "email": "kpdecker@gmail.com", + "url": "http://incaseofstairs.com" + } + ], + "bugs": { + "email": "kpdecker@gmail.com", + "url": "http://github.com/kpdecker/jsdiff/issues" + }, + "licenses": [ + { + "type": "BSD", + "url": "http://github.com/kpdecker/jsdiff/blob/master/LICENSE" + } + ], + "repository": { + "type": "git", + "url": "git://github.com/kpdecker/jsdiff.git" + }, + "engines": { + "node": ">=0.3.1" + }, + "main": "./diff", + "scripts": { + "test": "expresso test/*" + }, + "dependencies": {}, + "devDependencies": {}, + "_id": "diff@1.0.2", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "diff@1.0.2" +} diff --git a/node_modules/mocha/node_modules/diff/style.css b/node_modules/mocha/node_modules/diff/style.css new file mode 100644 index 0000000..2047e2d --- /dev/null +++ b/node_modules/mocha/node_modules/diff/style.css @@ -0,0 +1,81 @@ +* { + margin: 0; + padding: 0; +} +html, body { + background: #EEE; + font: 12px sans-serif; +} +body { + padding-top: 1.8em; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html, body, table, tbody, tr, td { + height: 100% +} +table { + table-layout: fixed; + width: 100%; +} +td { + width: 33%; + padding: 3px 4px; + border: 1px solid transparent; + vertical-align: top; + font: 1em monospace; + text-align: left; + white-space: pre-wrap; +} +h1 { + display: inline; + font-size: 100%; +} +del { + text-decoration: none; + color: #b30000; + background: #fadad7; +} +ins { + background: #eaf2c2; + color: #406619; + text-decoration: none; +} + +#settings { + position: absolute; + top: 0; + left: 5px; + right: 5px; + height: 2em; + line-height: 2em; +} +#settings label { + margin-left: 1em; +} + +.source { + position: absolute; + right: 1%; + top: .2em; +} + +[contentEditable] { + background: #F9F9F9; + border-color: #BBB #D9D9D9 #DDD; + border-radius: 4px; + -webkit-user-modify: read-write-plaintext-only; + outline: none; +} +[contentEditable]:focus { + background: #FFF; + border-color: #6699cc; + box-shadow: 0 0 4px #2175c9; +} + +@-moz-document url-prefix() { + body { + height: 99%; /* Hide scroll bar in Firefox */ + } +} diff --git a/node_modules/mocha/node_modules/diff/test/diffTest.js b/node_modules/mocha/node_modules/diff/test/diffTest.js new file mode 100644 index 0000000..311b48b --- /dev/null +++ b/node_modules/mocha/node_modules/diff/test/diffTest.js @@ -0,0 +1,616 @@ +const VERBOSE = false; + +var assert = require('assert'), + diff = require('../diff'); + +function log() { + VERBOSE && console.log.apply(console, arguments); +} + +exports['Whitespace diff'] = function() { + diffResult = diff.diffWords("New Value", "New ValueMoreData"); + assert.equal( + "New ValueMoreDataValue", + diff.convertChangesToXML(diffResult), + "Single whitespace diffResult Value"); + + diffResult = diff.diffWords("New Value ", "New ValueMoreData "); + assert.equal( + "New ValueMoreDataValue ", + diff.convertChangesToXML(diffResult), + "Multiple whitespace diffResult Value"); +}; + +// Diff on word boundary +exports['Word Diff'] = function() { + diffResult = diff.diffWords("New :Value:Test", "New ValueMoreData "); + assert.equal( + "New ValueMoreData :Value:Test", + diff.convertChangesToXML(diffResult), + "Nonmatching word boundary diffResult Value"); + diffResult = diff.diffWords("New Value:Test", "New Value:MoreData "); + assert.equal( + "New Value:MoreData Test", + diff.convertChangesToXML(diffResult), + "Word boundary diffResult Value"); + diffResult = diff.diffWords("New Value-Test", "New Value:MoreData "); + assert.equal( + "New Value:MoreData -Test", + diff.convertChangesToXML(diffResult), + "Uninque boundary diffResult Value"); + diffResult = diff.diffWords("New Value", "New Value:MoreData "); + assert.equal( + "New Value:MoreData ", + diff.convertChangesToXML(diffResult), + "Word boundary diffResult Value"); +}; + +// Diff without changes +exports['Diff without changes'] = function() { + diffResult = diff.diffWords("New Value", "New Value"); + assert.equal( + "New Value", + diff.convertChangesToXML(diffResult), + "No changes diffResult Value"); + diffResult = diff.diffWords("New Value", "New Value"); + assert.equal( + "New Value", + diff.convertChangesToXML(diffResult), + "No changes whitespace diffResult Value"); + diffResult = diff.diffWords("", ""); + assert.equal( + "", + diff.convertChangesToXML(diffResult), + "Empty no changes diffResult Value"); +}; + +// Empty diffs +exports['Empty diffs'] = function() { + diffResult = diff.diffWords("New Value", ""); + assert.equal(1, diffResult.length, "Empty diff result length"); + assert.equal( + "New Value", + diff.convertChangesToXML(diffResult), + "Empty diffResult Value"); + diffResult = diff.diffWords("", "New Value"); + assert.equal( + "New Value", + diff.convertChangesToXML(diffResult), + "Empty diffResult Value"); +}; + +// With without anchor (the Heckel algorithm error case) +exports['No anchor'] = function() { + diffResult = diff.diffWords("New Value New Value", "Value Value New New"); + assert.eql( + "ValueNew Value New NewValue", + diff.convertChangesToXML(diffResult), + "No anchor diffResult Value"); +}; + +// CSS Diff +exports['CSS diffs'] = function() { + diffResult = diff.diffCss( + ".test,#value .test{margin-left:50px;margin-right:-40px}", + ".test2, #value2 .test {\nmargin-top:50px;\nmargin-right:-400px;\n}"); + assert.equal( + ".test2.test,#value #value2 .test {\n" + + "margin-topmargin-left:50px;\n" + + "margin-right:-400px;\n-40px}", + diff.convertChangesToXML(diffResult), + "CSS diffResult Value"); +}; + +// Line Diff +exports['Line diffs'] = function() { + diffResult = diff.diffLines( + "line\nold value\nline", + "line\nnew value\nline"); + assert.equal( + "line\nnew value\nold value\nline", + diff.convertChangesToXML(diffResult), + "Line diffResult Value"); + diffResult = diff.diffLines( + "line\nvalue\nline", + "line\nvalue\nline"); + assert.equal( + "line\nvalue\nline", + diff.convertChangesToXML(diffResult), + "Line same diffResult Value"); + diffResult = diff.diffLines( + "line\nvalue \nline", + "line\nvalue\nline"); + log("diffResult", diffResult); + log("diffResult", diff.convertChangesToXML(diffResult)); + assert.equal( + "line\nvalue\nvalue \nline", + diff.convertChangesToXML(diffResult), + "Line whitespace diffResult Value"); +}; + +// Patch creation with diff at EOF +exports['lastLineChanged'] = function() { + assert.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + ' line5\n', + diff.createPatch('test', 'line2\nline3\nline5\n', 'line2\nline3\nline4\nline5\n', 'header1', 'header2')); + + assert.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '+line5\n', + diff.createPatch('test', 'line2\nline3\nline4\n', 'line2\nline3\nline4\nline5\n', 'header1', 'header2')); + + assert.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +1,4 @@\n' + + ' line1\n' + + ' line2\n' + + ' line3\n' + + '+line44\n' + + '-line4\n', + diff.createPatch('test', 'line1\nline2\nline3\nline4\n', 'line1\nline2\nline3\nline44\n', 'header1', 'header2')); + + assert.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +1,5 @@\n' + + ' line1\n' + + ' line2\n' + + ' line3\n' + + '+line44\n' + + '+line5\n' + + '-line4\n', + diff.createPatch('test', 'line1\nline2\nline3\nline4\n', 'line1\nline2\nline3\nline44\nline5\n', 'header1', 'header2')); +}; + +exports['EOFNL'] = function() { + assert.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +1,4 @@\n' + + ' line1\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + '\\ No newline at end of file\n' + + '-line4\n', + diff.createPatch('test', 'line1\nline2\nline3\nline4\n', 'line1\nline2\nline3\nline4', 'header1', 'header2')); + + assert.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +1,4 @@\n' + + ' line1\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + '-line4\n' + + '\\ No newline at end of file\n', + diff.createPatch('test', 'line1\nline2\nline3\nline4', 'line1\nline2\nline3\nline4\n', 'header1', 'header2')); + + assert.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +1,4 @@\n' + + '+line1\n' + + '-line11\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + '\\ No newline at end of file\n', + diff.createPatch('test', 'line11\nline2\nline3\nline4', 'line1\nline2\nline3\nline4', 'header1', 'header2')); + + assert.eql( + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,5 +1,5 @@\n' + + '+line1\n' + + '-line11\n' + + ' line2\n' + + ' line3\n' + + ' line4\n' + + ' line4\n', + diff.createPatch('test', 'line11\nline2\nline3\nline4\nline4\nline4\nline4', 'line1\nline2\nline3\nline4\nline4\nline4\nline4', 'header1', 'header2')); +}; + +exports['Large Test'] = function() { + var random = 42; + var mult = 134775813, range = Math.pow(2, 32); + function nextRandom() { + random = ((random * mult) + 1) % range; + return random; + } + var largeTest = ".hbh9asgiidc {ehaahc9:ses;bhg9hc:ses;idgaag-hi9aa:cdca;ihgd9gdgca-gdadg:ighchehgaci;ggghdg:edhciag;daagsada:ahhhiaa;ahai7:hgid;}.hbh9asgiidc.hchgihaa {ggghdg:hgid;}.igiidchbh9ah {ihgd9gdgca-hbh9a:gga(" + + "hbh9ah/igiidcfhbh9ah.9hs);ihgd9gdgca-gaeahi:cd-gaeahi;7ah97i:7des;bhg9hc-gh97i:ses;ahai7:7des;ihgd9gdgca-edhhihdc:ses ses;}.igiidcfgde9 {ihgd9gdgca-edhhihdc:ses ses;}.bdghadaag .igiidcfgde9 {ihgd9gdgc" + + "a-edhhihdc:-7des ses;}.hchgihaa .igiidcfgde9 {ihgd9gdgca-edhhihdc:-dses ses;}.igiidcfaaaaia {ihgd9gdgca-edhhihdc:-bdes ses;}.bdghadaag .igiidcfaaaaia {ihgd9gdgca-edhhihdc:-9sses ses;}.hchgihaa .igiidc" + + "faaaaia {ihgd9gdgca-edhhihdc:-97des ses;}.igiidcfadacadha {ihgd9gdgca-edhhihdc:-9dses ses;}.bdghadaag .igiidcfadacadha {ihgd9gdgca-edhhihdc:-9bdes ses;}.hchgihaa .igiidcfadacadha {ihgd9gdgca-edhhihdc:" + + "-7sses ses;}.igiidcfabhha {ihgd9gdgca-edhhihdc:-77des ses;}.bdghadaag .igiidcfabhha {ihgd9gdgca-edhhihdc:-7dses ses;}.hchgihaa .igiidcfabhha {ihgd9gdgca-edhhihdc:-7bdes ses;}.igiidcfbdaa {ihgd9gdgca-e" + + "dhhihdc:-d7des ses;}.bdghadaag .igiidcfbdaa {ihgd9gdgca-edhhihdc:-ddses ses;}.hchgihaa .igiidcfbdaa {ihgd9gdgca-edhhihdc:-dbdes ses;}.igiidcfcaasdaaag {ihgd9gdgca-edhhihdc:-abdes ses;}.bdghadaag .igii" + + "dcfcaasdaaag {ihgd9gdgca-edhhihdc:-bsses ses;}.hchgihaa .igiidcfcaasdaaag {ihgd9gdgca-edhhihdc:-b7des ses;}.igiidcfgachba {ihgd9gdgca-edhhihdc:-dbdes ses;}.bdghadaag .igiidcfgachba {ihgd9gdgca-edhhihd" + + "c:-9ssses ses;}.hchgihaa .igiidcfgachba {ihgd9gdgca-edhhihdc:-9s7des ses;}.igiidcfghh {ihgd9gdgca-edhhihdc:-9sdses ses;}.bdghadaag .igiidcfghh {ihgd9gdgca-edhhihdc:-9sbdes ses;}.hchgihaa .igiidcfghh {" + + "ihgd9gdgca-edhhihdc:-99sses ses;}.igiidcfh7hga {ihgd9gdgca-edhhihdc:-97bdes ses;}.bdghadaag .igiidcfh7hga {ihgd9gdgca-edhhihdc:-9hsses ses;}.hchgihaa .igiidcfh7hga {ihgd9gdgca-edhhihdc:-9h7des ses;}.i" + + "giidcfgeadha {ihgd9gdgca-edhhihdc:-9hdses ses;}.bdghadaag .igiidcfgeadha {ihgd9gdgca-edhhihdc:-9hbdes ses;}.hchgihaa .igiidcfgeadha {ihgd9gdgca-edhhihdc:-9gsses ses;}.igiidcfaaisdaaag {ihgd9gdgca-edhh" + + "ihdc:-9dsses ses;}.bdghadaag .igiidcfaaisdaaag {ihgd9gdgca-edhhihdc:-9d7des ses;}.hchgihaa .igiidcfaaisdaaag {ihgd9gdgca-edhhihdc:-9ddses ses;}.igiidcfabei9ighh7 {ihgd9gdgca-edhhihdc:-hsses ses;}.bdgh" + + "adaag .igiidcfabei9ighh7 {ihgd9gdgca-edhhihdc:-h7des ses;}.hchgihaa .igiidcfabei9ighh7 {ihgd9gdgca-edhhihdc:-hdses ses;}.igiidcfadgd {ihgd9gdgca-edhhihdc:-hbdes ses;}.bdghadaag .igiidcfadgd {ihgd9gdgc" + + "a-edhhihdc:-gsses ses;}.hchgihaa .igiidcfadgd {ihgd9gdgca-edhhihdc:-g7des ses;}.igiidcfbhch9a {ihgd9gdgca-edhhihdc:-gdses ses;}.bdghadaag .igiidcfbhch9a {ihgd9gdgca-edhhihdc:-gbdes ses;}.hchgihaa .igi" + + "idcfbhch9a {ihgd9gdgca-edhhihdc:-dsses ses;}.igiidcfcaaahdh {ihgd9gdgca-edhhihdc:-bdses ses;}.bdghadaag .igiidcfcaaahdh {ihgd9gdgca-edhhihdc:-bbdes ses;}.hchgihaa .igiidcfcaaahdh {ihgd9gdgca-edhhihdc:" + + "-csses ses;}.igiidcfhhaahahgg7gahgaih {ihgd9gdgca-edhhihdc:-97sses ses;}.bdghadaag .igiidcfhhaahahgg7gahgaih {ihgd9gdgca-edhhihdc:-977des ses;}.hchgihaa .igiidcfhhaahahgg7gahgaih {ihgd9gdgca-edhhihdc:" + + "-97dses ses;}.igiidcfhhaahahgg7 {ihgd9gdgca-edhhihdc:-997des ses;}.bdghadaag .igiidcfhhaahahgg7 {ihgd9gdgca-edhhihdc:-99dses ses;}.hchgihaa .igiidcfhhaahahgg7 {ihgd9gdgca-edhhihdc:-99bdes ses;}.igiidc" + + "fcaaidddbhgd {ihgd9gdgca-edhhihdc:-asses ses;}.bdghadaag .igiidcfcaaidddbhgd {ihgd9gdgca-edhhihdc:-a7des ses;}.hchgihaa .igiidcfcaaidddbhgd {ihgd9gdgca-edhhihdc:-adses ses;}.igiidcfdeac {ihgd9gdgca-ed" + + "hhihdc:-c7des ses;}.bdghadaag .igiidcfdeac {ihgd9gdgca-edhhihdc:-cdses ses;}.hchgihaa .igiidcfdeac {ihgd9gdgca-edhhihdc:-cbdes ses;}.igiidcfdaagaghia {ihgd9gdgca-edhhihdc:-9hdses ses;}.bdghadaag .igii" + + "dcfdaagaghia {ihgd9gdgca-edhhihdc:-9hbdes ses;}.hchgihaa .igiidcfdaagaghia {ihgd9gdgca-edhhihdc:-9gsses ses;}.igiidcfahaa {ihgd9gdgca-edhhihdc:-9g7des ses;}.bdghadaag .igiidcfahaa {ihgd9gdgca-edhhihdc" + + ":-9gdses ses;}.hchgihaa .igiidcfahaa {ihgd9gdgca-edhhihdc:-9gbdes ses;}.igiidcfih9 {ihgd9gdgca-edhhihdc:-9dbdes ses;}.bdghadaag .igiidcfih9 {ihgd9gdgca-edhhihdc:-9asses ses;}.hchgihaa .igiidcfih9 {ihg" + + "d9gdgca-edhhihdc:-9a7des ses;}.igiidcfhgihgghia {ihgd9gdgca-edhhihdc:-9b7des ses;}.bdghadaag .igiidcfhgihgghia {ihgd9gdgca-edhhihdc:-9bdses ses;}.hchgihaa .igiidcfhgihgghia {ihgd9gdgca-edhhihdc:-9bbde" + + "s ses;}.igiidcfhgeaggaaa {ihgd9gdgca-edhhihdc:-9csses ses;}.bdghadaag .igiidcfhgeaggaaa {ihgd9gdgca-edhhihdc:-9c7des ses;}.hchgihaa .igiidcfhgeaggaaa {ihgd9gdgca-edhhihdc:-9cdses ses;}.igiidcfggahiaha" + + "ghah {ihgd9gdgca-edhhihdc:-9csses ses;}.bdghadaag .igiidcfggahiahaghah {ihgd9gdgca-edhhihdc:-9c7des ses;}.hchgihaa .igiidcfggahiahaghah {ihgd9gdgca-edhhihdc:-9cdses ses;}.igiidcfggahiagagdgaghia9dg9 {" + + "ihgd9gdgca-edhhihdc:-9cbdes ses;}.bdghadaag .igiidcfggahiagagdgaghia9dg9 {ihgd9gdgca-edhhihdc:-9dsses ses;}.hchgihaa .igiidcfggahiagagdgaghia9dg9 {ihgd9gdgca-edhhihdc:-9d7des ses;}.igiidcfggahiagagdga" + + "sdaaag {ihgd9gdgca-edhhihdc:-9ddses ses;}.bdghadaag .igiidcfggahiagagdgasdaaag {ihgd9gdgca-edhhihdc:-9dbdes ses;}.hchgihaa .igiidcfggahiagagdgasdaaag {ihgd9gdgca-edhhihdc:-7ssses ses;}.igiidcfggahiaga" + + "gdga {ihgd9gdgca-edhhihdc:-7s7des ses;}.bdghadaag .igiidcfggahiagagdga {ihgd9gdgca-edhhihdc:-7sdses ses;}.hchgihaa .igiidcfggahiagagdga {ihgd9gdgca-edhhihdc:-7sbdes ses;}.igiidcfggahiae79hhghagagdga {" + + "ihgd9gdgca-edhhihdc:-79bdes ses;}.bdghadaag .igiidcfggahiae79hhghagagdga {ihgd9gdgca-edhhihdc:-77sses ses;}.hchgihaa .igiidcfggahiae79hhghagagdga {ihgd9gdgca-edhhihdc:-777des ses;}.igiidcfbhdagagdga {" + + "ihgd9gdgca-edhhihdc:-77dses ses;}.bdghadaag .igiidcfbhdagagdga {ihgd9gdgca-edhhihdc:-77bdes ses;}.hchgihaa .igiidcfbhdagagdga {ihgd9gdgca-edhhihdc:-7hsses ses;}.igiidcfggahiagagdgaaaghhdc {ihgd9gdgca-" + + "edhhihdc:-79sses ses;}.bdghadaag .igiidcfggahiagagdgaaaghhdc {ihgd9gdgca-edhhihdc:-797des ses;}.hchgihaa .igiidcfggahiagagdgaaaghhdc {ihgd9gdgca-edhhihdc:-79dses ses;}.igiidcfgaea9abhha {ihgd9gdgca-ed" + + "hhihdc:-7h7des ses;}.bdghadaag .igiidcfgaea9abhha {ihgd9gdgca-edhhihdc:-7hdses ses;}.hchgihaa .igiidcfgaea9abhha {ihgd9gdgca-edhhihdc:-7hbdes ses;}.igiidcfsdgahgaabhha {ihgd9gdgca-edhhihdc:-9b7des ses" + + ";}.bdghadaag .igiidcfsdgahgaabhha {ihgd9gdgca-edhhihdc:-9bdses ses;}.hchgihaa .igiidcfsdgahgaabhha {ihgd9gdgca-edhhihdc:-9bbdes ses;}.igiidcfegiihgdabhha {ihgd9gdgca-edhhihdc:-7d7des ses;}.bdghadaag ." + + "igiidcfegiihgdabhha {ihgd9gdgca-edhhihdc:-7ddses ses;}.hchgihaa .igiidcfegiihgdabhha {ihgd9gdgca-edhhihdc:-7dbdes ses;}.igiidcfhhaa {ihgd9gdgca-edhhihdc:-7a7des ses;}.bdghadaag .igiidcfhhaa {ihgd9gdgc" + + "a-edhhihdc:-7adses ses;}.hchgihaa .igiidcfhhaa {ihgd9gdgca-edhhihdc:-7abdes ses;}.igiidcfhhaa {ihgd9gdgca-edhhihdc:-7a7des ses;}.bdghadaag .igiidcfhhaa {ihgd9gdgca-edhhihdc:-7adses ses;}.hchgihaa .igi" + + "idcfhhaa {ihgd9gdgca-edhhihdc:-7abdes ses;}.igiidcfaahi {ihgd9gdgca-edhhihdc:-h9shes ses;}.bdghadaag .igiidcfaahi {ihgd9gdgca-edhhihdc:-h97ces ses;}.hchgihaa .igiidcfaahi {ihgd9gdgca-edhhihdc:-h9dhes " + + "ses;}.igiidcfhaagdaa {ihgd9gdgca-edhhihdc:-h9bces ses;}.bdghadaag .igiidcfhaagdaa {ihgd9gdgca-edhhihdc:-h7shes ses;}.hchgihaa .igiidcfhaagdaa {ihgd9gdgca-edhhihdc:-h77ces ses;}.igiidcfcaagdcihgi {ihgd" + + "9gdgca-edhhihdc:-h7dhes ses;}.bdghadaag .igiidcfcaagdcihgi {ihgd9gdgca-edhhihdc:-h7bces ses;}.hchgihaa .igiidcfcaagdcihgi {ihgd9gdgca-edhhihdc:-hhshes ses;}.igiidcfcaa9gdge {ihgd9gdgca-edhhihdc:-hh7ce" + + "s ses;}.bdghadaag .igiidcfcaa9gdge {ihgd9gdgca-edhhihdc:-hhdhes ses;}.hchgihaa .igiidcfcaa9gdge {ihgd9gdgca-edhhihdc:-hhbces ses;}.igiidcf7aae {ihgd9gdgca-edhhihdc:-hgshes ses;}.bdghadaag .igiidcf7aae" + + " {ihgd9gdgca-edhhihdc:-hg7ces ses;}.igiidcfagdebacg {ihgd9gdgca-edhhihdc:-hscces ses;ahai7:9ges;}.bdghadaag .igiidcfagdebacg {ihgd9gdgca-edhhihdc:-hsbges ses;}.hchgihaa .igiidcfagdebacg {ihgd9gdgca-ed" + + "hhihdc:-hscces ses;}.igiidcfighchsaghc {ihgd9gdgca-edhhihdc:-7bbdes ses;}.bdghadaag .igiidcfighchsaghc {ihgd9gdgca-edhhihdc:-7csses ses;}.hchgihaa .igiidcfighchsaghc {ihgd9gdgca-edhhihdc:-7c7des ses;}" + + ".igiidcfhihgiadgdsada {ihgd9gdgca-edhhihdc:-hgdhes ses;}.bdghadaag .igiidcfhihgiadgdsada {ihgd9gdgca-edhhihdc:-hgbces ses;}.hchgihaa .igiidcfhihgiadgdsada {ihgd9gdgca-edhhihdc:-hdshes ses;}.igiidcfgas" + + "gah7 {ihgd9gdgca-edhhihdc:-hd7ces ses;}.bdghadaag .igiidcfgasgah7 {ihgd9gdgca-edhhihdc:-hddhes ses;}.hchgihaa .igiidcfgasgah7 {ihgd9gdgca-edhhihdc:-hdbces ses;}.igiidcfgadhaagdeids {ihgd9gdgca-edhhihd" + + "c:-hashes ses;}.bdghadaag .igiidcfgadhaagdeids {ihgd9gdgca-edhhihdc:-ha7ces ses;}.hchgihaa .igiidcfgadhaagdeids {ihgd9gdgca-edhhihdc:-hadhes ses;}.igiidcfdeacagdeids {ihgd9gdgca-edhhihdc:-habces ses;}" + + ".bdghadaag .igiidcfdeacagdeids {ihgd9gdgca-edhhihdc:-hbshes ses;}.hchgihaa .igiidcfdeacagdeids {ihgd9gdgca-edhhihdc:-hb7ces ses;}.igiidcfcaaagdeids {ihgd9gdgca-edhhihdc:-hbdges ses;}.bdghadaag .igiidc" + + "fcaaagdeids {ihgd9gdgca-edhhihdc:-hbbdes ses;}.hchgihaa .igiidcfcaaagdeids {ihgd9gdgca-edhhihdc:-hcsges ses;}.igiidcfighh7gahidga {ihgd9gdgca-edhhihdc:-hc7des ses;}.bdghadaag .igiidcfighh7gahidga {ihg" + + "d9gdgca-edhhihdc:-hcdges ses;}.hchgihaa .igiidcfighh7gahidga {ihgd9gdgca-edhhihdc:-hcbdes ses;}.hgdchbh9ah {ihgd9gdgca-hbh9a:gga(hbh9ah/hgdcfhbh9ah.9hs);ihgd9gdgca-gaeahi:cd-gaeahi;7ah97i:77es;ahca-7a" + + "h97i:77es;bhg9hc-gh97i:ses;ahai7:7des;ihgd9gdgca-edhhihdc:ses ses;}.hgdcfidddbhgdggggaci {ihgd9gdgca-edhhihdc:-ses ses;}.hgdcfidddbhgdggggacif7daag {ihgd9gdgca-edhhihdc:-7des ses;}.hgdcfidddbhgdggggac" + + "ifhchgihaa {ihgd9gdgca-edhhihdc:-dses ses;}.hgdcfasehca {ihgd9gdgca-edhhihdc:-bdes ses;}.hgdcfasehcaf7daag {ihgd9gdgca-edhhihdc:-9sses ses;}.hgdcfbhch9aggggaci {ihgd9gdgca-edhhihdc:-97des ses;}.hgdcfb" + + "hch9aggggacif7daag {ihgd9gdgca-edhhihdc:-9dses ses;}.hgdcfbhch9aggggacifhchgihaa {ihgd9gdgca-edhhihdc:-9bges ses;}.hgdcfgasgah7ggggaci {ihgd9gdgca-edhhihdc:-7sses ses;}.hgdcfgasgah7ggggacifhchgihaa {i" + + "hgd9gdgca-edhhihdc:-ah9es ses;}.hgdcfgasgah7ggggacif7daag {ihgd9gdgca-edhhihdc:-77des ses;}.hgdcfidddbhgdh {ahai7:7ses;ihgd9gdgca-edhhihdc:-7dses ses;}.hgdcfidddbhgdhf7daag {ahai7:7ses;ihgd9gdgca-edhh" + + "ihdc:-7bses ses;}.hgdcfge {ahai7:7ses;ihgd9gdgca-edhhihdc:-7cdes ses;}.hgdcfgef7daag {ahai7:7ses;ihgd9gdgca-edhhihdc:-hsdes ses;}.hgdcfgefhchgihaa {ahai7:7ses;ihgd9gdgca-edhhihdc:-h7des ses;}.hgdcfhah" + + "gg7 {ihgd9gdgca-edhhihdc:-hgdes ses;}.hgdcfhahgg7fhchgihaa {ihgd9gdgca-edhhihdc:-asaes ses;}.hgdcfhahgg7f7daag {ihgd9gdgca-edhhihdc:-hbges ses;}.hgdcfgdaaheha {ihgd9gdgca-edhhihdc:-gaaes ses;}.hgdcfgd" + + "aahehaf7daag {ihgd9gdgca-edhhihdc:-gd9es ses;}.hgdcfaggdg {ihgd9gdgca-edhhihdc:-g9hes ses;7ah97i:9ges;}.hgdcf7aae {ihgd9gdgca-edhhihdc:-gh9es ses;7ah97i:9ces;}.hgdcfhcsd {ihgd9gdgca-edhhihdc:-ggdes se" + + "s;7ah97i:9ges;}.hgdcfgddisdaaag {ihgd9gdgca-edhhihdc:-gades ses;7ah97i:7ses;}.hgdcfihgd {ihgd9gdgca-edhhihdc:-d9aes ses;ahai7 :7ses;}.hgdcfihgdf7daag {ihgd9gdgca-edhhihdc:-dhaes ses;ahai7 :7ses;}.hgdc" + + "fgadhafhahgg7 {ihgd9gdgca-edhhihdc:-ddaes ses;ahai7 :7des;}.hgdcfgadhafhahgg7f7daag {ihgd9gdgca-edhhihdc:-dc9es ses;ahai7:7des;}.gbhgdc {ihgd9gdgca-hbh9a:gga(hbh9ah/hgdchfaf.9hs);ihgd9gdgca-gaeahi:cd-" + + "gaeahi;7ah97i:7ses;bhg9hc-gh97i:ses;aagihgha-hah9c:bhaaaa;ahai7:7ses;ihgd9gdgca-edhhihdc:ses ses;}.haghah {ihgd9gdgca-edhhihdc:-ses ses;}.gagfghia9dg9 {ihgd9gdgca-edhhihdc:-7ses ses;}.gagfsdaaag {ihgd" + + "9gdgca-edhhihdc:-gses ses;}.gagfsdaaagfhahhifsa {ihgd9gdgca-edhhihdc:-9gses ses;}.gagfsdaaagfahhedhaa {ihgd9gdgca-edhhihdc:-9ases ses;}.gagfsdaaagfsgd7ac {ihgd9gdgca-edhhihdc:-97ses ses;}.gagdgafe79hh" + + "gha {ihgd9gdgca-edhhihdc:-9sses ses;}.gagdgafabhha {ihgd9gdgca-edhhihdc:-cses ses;}.gagdga {ihgd9gdgca-edhhihdc:-ases ses;}.gagdgafhahhifsa {ihgd9gdgca-edhhihdc:-7sses ses;}.gagdgafahhedhaa {ihgd9gdgc" + + "a-edhhihdc:-77ses ses;}.gagdgafsgd7ac {ihgd9gdgca-edhhihdc:-9cses ses;}.gbhgdc {ihgd9gdgca-hbh9a:gga(hbh9ah/hgdchfaf.9hs);ihgd9gdgca-gaeahi:cd-gaeahi;7ah97i:7ses;bhg9hc-gh97i:ses;aagihgha-hah9c:bhaaaa" + + ";ahai7:7ses;ihgd9gdgca-edhhihdc:ses ses;}.haghah {ihgd9gdgca-edhhihdc:-ses ses;}.gagfghia9dg9 {ihgd9gdgca-edhhihdc:-7ses ses;}.gagfsdaaag {ihgd9gdgca-edhhihdc:-gses ses;}.gagfsdaaagfhahhifsa {ihgd9gdg" + + "ca-edhhihdc:-9gses ses;}.gagfsdaaagfahhedhaa {ihgd9gdgca-edhhihdc:-9ases ses;}.gagfsdaaagfsgd7ac {ihgd9gdgca-edhhihdc:-97ses ses;}.gagdgafe79hhgha {ihgd9gdgca-edhhihdc:-9sses ses;}.gagdgafabhha {ihgd9" + + "gdgca-edhhihdc:-cses ses;}.gagdga {ihgd9gdgca-edhhihdc:-ases ses;}.gagdgafhahhifsa {ihgd9gdgca-edhhihdc:-7sses ses;}.gagdgafahhedhaa {ihgd9gdgca-edhhihdc:-77ses ses;}.gagdgafsgd7ac {ihgd9gdgca-edhhihd" + + "c:-9cses ses;}.shaahgdc {ihgd9gdgca-hbh9a:gga(hbh9ah/gdcghiachihdc.9hs);ihgd9gdgca-gaeahi:cd-gaeahi;7ah97i:7ses;bhg9hc-gh97i:ses;aagihgha-hah9c:bhaaaa;ahai7:7ses;ihgd9gdgca-edhhihdc:ses ses;}.shaa{ihg" + + "d9gdgca-edhhihdc:ses ses;}.shaafhggdihi{ihgd9gdgca-edhhihdc:-7ses ses;}.shaafheeaa{ihgd9gdgca-edhhihdc:-gses ses;}.shaafheeahghihdc{ihgd9gdgca-edhhihdc:-ases ses;}.shaafhgahd{ihgd9gdgca-edhhihdc:-cses" + + " ses;}.shaafasgaa{ihgd9gdgca-edhhihdc:-9sses ses;}.shaaf7iba{ihgd9gdgca-edhhihdc:-97ses ses;}.shaafhbh9a{ihgd9gdgca-edhhihdc:-9gses ses;}.shaafghah{ihgd9gdgca-edhhihdc:-9ases ses;}.shaafghahhgghei{ihg" + + "d9gdgca-edhhihdc:-9cses ses;}.shaafbhadga{ihgd9gdgca-edhhihdc:-7sses ses;}.shaafeei{ihgd9gdgca-edhhihdc:-77ses ses;}.shaafegdgagi{ihgd9gdgca-edhhihdc:-7gses ses;}.shaaffghgdihba{ihgd9gdgca-edhhihdc:-7" + + "ases ses;}.shaafiasi{ihgd9gdgca-edhhihdc:-7cses ses;}.shaafahaad{ihgd9gdgca-edhhihdc:-hsses ses;}.ahdh{ihgd9gdgca-edhhihdc:-hcses ses;}.shaafsba{ihgd9gdgca-edhhihdc:-h7ses ses;}.shaaf7he{ihgd9gdgca-ed" + + "hhihdc:-hgses ses;}.sdaaag{ihgd9gdgca-edhhihdc:-hases ses;}.shaafagdeids {ihgd9gdgca-edhhihdc:-gsses ses;}.aaaaiafhbhaa{ihgd9gdgca-edhhihdc:-ggses ses;ggghdg:edhciag;}.egiahgfh7hga{ihgd9gdgca-edhhihdc" + + ":-gases ses;}.h7hga{ihgd9gdgca-edhhihdc:-gcses ses;}.eghahia{ihgd9gdgca-edhhihdc:-dsses ses;}.gddisdaaag{ihgd9gdgca-edhhihdc:-dcses ses;}.adacfhggda{ihgd9gdgca-edhhihdc:-a77es ses;ahai7:9ces;}.7aae{ih" + + "gd9gdgca-edhhihdc:-agses ses;}.ighh7 {ihgd9gdgca-edhhihdc:-acses ses;}.hggdafgh97i {ihgd9gdgca-edhhihdc:-bs7es ses;ahai7:9ces;}.igahagggbih {a7hia-hehga:cdaghe;}.igahagggbih .igahagggbihfgddi {aagihgh" + + "a-hah9c:bhaaaa;ehaahc9:s;}.igahagggbih .igahagggbihfhggda {aagihgha-hah9c:bhaaaa;ehaahc9-aasi:7es;}.igahagggbih .igahagggbihfahgchba {aagihgha-hah9c:bhaaaa;ehaahc9-aasi:7es;sdci-shbha9:aghha,aaaaaihgh" + + ",hhch-haghs;sdci-hh7a:9ab;}.igahagggbih .igahagggbihfahgchba hcegi {ihgd9gdgca:ighchehgaci;idgaag:cdca;ehaahc9:ses;bhg9hc:ses;ahai7:9ss%;}a {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;}a:ahcd {iasi" + + "-aagdghihdc:cdca;gdadg:#ssssss;}a:ahhhiaa {iasi-aagdghihdc:cdca;gdadg:#ssssss;}a:hgihaa {iasi-aagdghihdc:cdca;gdadg:#ssssss;}a:ahcd.iddaihgfigiidcfahcd {iasi-aagdghihdc:cdca;gdadg:#ssssss;}a:ahhhiaa.i" + + "ddaihgfigiidcfahcd {iasi-aagdghihdc:cdca;gdadg:#ssssss;}a:hgihaa.iddaihgfigiidcfahcd {iasi-aagdghihdc:cdca;gdadg:#ssssss;}a:7daag.iddaihgfigiidcfahcd {iasi-aagdghihdc:cdca;gdadg:#ggbggg;ggghdg:edhc" + + "iag;}a:ahcd.aaaaa9fiasi {iasi-aagdghihdc:cdca;gdadg:#gggggg;sdci-hh7a:dd%;}a:ahhhiaa.aaaaa9fiasi {iasi-aagdghihdc:cdca;gdadg:#gggggg;sdci-hh7a:dd%;}a:hgihaa.aaaaa9fiasi {iasi-aagdghihdc:gcaagahca;gdad" + + "g:#ssssss;sdci-hh7a:dd%;}a:7daag.aaaaa9fiasi {iasi-aagdghihdc:cdca;gdadg:#ggbggg;sdci-hh7a:dd%;ggghdg:edhciag;}a:ahcd.aaaaa9fhgihaafhagihdc {iasi-aagdghihdc:cdca;gdadg:#gggggg;sdci-aah97i:idaa;sdci-hh" + + "7a:dd%;}a:ahhhiaa.aaaaa9fhgihaafhagihdc {iasi-aagdghihdc:cdca;gdadg:#gggggg;sdci-aah97i:idaa;sdci-hh7a:dd%;}a:hgihaa.aaaaa9fhgihaafhagihdc {iasi-aagdghihdc:gcaagahca;gdadg:#ssssss;sdci-aah97i:idaa;sdc" + + "i-hh7a:dd%;}a:7daag.aaaaa9fhgihaafhagihdc {iasi-aagdghihdc:cdca;gdadg:#ggbggg;sdci-hh7a:dd%;sdci-aah97i:idaa;ggghdg:edhciag;}a:ahcd.aaaaa7fiasi {iasi-aagdghihdc:cdca;gdadg:#cdcdcd;}a:ahhhiaa.aaaaa7fia" + + "si {iasi-aagdghihdc:cdca;gdadg:#cdcdcd;}a:hgihaa.aaaaa7fiasi {iasi-aagdghihdc:gcaagahca;gdadg:#cdcdcd;}a:7daag.aaaaa7fiasi {iasi-aagdghihdc:gcaagahca;gdadg:#ggbggg;ggghdg:edhciag;}a:ahcd.aaaaahfiasi {" + + "iasi-aagdghihdc:cdca;gdadg:#cdcdcd;ahca-7ah97i:7ses;}a:ahhhiaa.aaaaahfiasi {iasi-aagdghihdc:cdca;gdadg:#cdcdcd;ahca-7ah97i:7ses;}a:hgihaa.aaaaahfiasi {iasi-aagdghihdc:gcaagahca;gdadg:#cdcdcd;ahca-7ah9" + + "7i:7ses;}a:7daag.aaaaahfiasi {iasi-aagdghihdc:cdca;gdadg:#ggbggg;ggghdg:edhciag;ahca-7ah97i:7ses;}a:ahcd.gcaagahca {gdadg:#ssssss;iasi-aagdghihdc:gcaagahca;ggghdg:edhciag;}a:ahhhiaa.gcaagahca {gdadg:#" + + "ssssss;iasi-aagdghihdc:gcaagahca;}a:hgihaa.gcaagahca {iasi-aagdghihdc:gcaagahca;gdadg:#ssssss;}a:7daag.gcaagahca {iasi-aagdghihdc:gcaagahca;gdadg:#ggbggg;}.igahagggbih hcegi {ggghdg:edhciag;}.igahaggg" + + "bih hcegi:7daag.hchgihaa {ggghdg:aashgai;gdadg:#adadad;}a:7daag,a.sdggh,a:ahcd.sdggh,a:ahhhiaa.sdggh,a:7daag.sdggh,a:hgihaa.sdggh,.ihiehhi igiidc:7daag,.igahagggbih hcegi:7daag {iasi-aagdghihdc:cdca;g" + + "dadg:#ggbggg;ggghdg:edhciag;}h hb9 {idgaag:cdca;}ida9 {ehaahc9-gh97i:ses;ehaahc9-aasi:ses;ehaahc9-idiidb:ses;ehaahc9-ide:ses;bhg9hc-gh97i:ses;bhg9hc-aasi:ses;bhg9hc-ide:ses;bhg9hc-idiidb:ses;ihgd9gdgc" + + "a-gdadg:#gggggg;sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:bg%;}ihiaa {sdci-hh7a:9ab;}ihiaa ihiaa {sdci-hh7a:9ss%;}.aasigadhi {sadhi:aasi;}i {sdci-aah97i:idaa;}.igiidc {sdci-shbha9:sh7d" + + "bh,aghha,aaaaaihgh,hhch-haghs;ehaahc9:des;iasi-hah9c:gaciag;sadhi:aasi;}.ihhhgsgiidc {ehaahc9:ses;bhg9hc:ses;idgaag-hi9aa:cdca;ihgd9gdgca-gdadg:ighchehgaci;ggghdg:edhciag;}.ihhhgsgiidc .igiidcfiasi {a" + + "hca-7ah97i:7ses;sdci-hh7a:9ab;}.ihhhgsgiidc.bdghadaag .igiidcfiasi {gdadg:#ggbggg;}.ihhhgsgiidc.hchgihaa .igiidcfiasi {gdadg:#sgsgsg;ggghdg:aashgai;}.aasifigiidc {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hh" + + "ch-haghs;sdci-hh7a:s.dab;ehaahc9-ide:des;ehaahc9-aasi:des;ehaahc9-gh97i:des;iasi-hah9c:gaciag;sadhi:aasi;bhc-ahai7:dses;}.gh97ifigiidc {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:s.dab;eh" + + "aahc9:des;iasi-hah9c:gaciag;sadhi:gh97i;}.gdagbcf7ahaag {sdci-aah97i:idaa;sdci-hh7a:dd%;ahca-7ah97i:9des;gdadg:#ssssss;ehaahc9-ide:7es;ehaahc9-aasi:7es;ehaahc9-gh97i:7es;a7hia-hehga:cdaghe;iasi-hah9c:" + + "aasi;}.hgdcfgdagbcf7ahaag {ehaahc9-aasi:7des;}.ahihfgda i7 {iasi-hah9c:aasi;sdci-aah97i:cdgbha;}.ahihfgda .cd7hih {iasi-hah9c:gaciag;}#idddbhgdfihiaa i7 h {ehaahc9-aasi:9ses;}.ahihfgda {7ah97i:7des;ih" + + "gd9gdgca-gdadg:#gggggg;}.ahihfahhi9dagbc {ehaahc9-aasi:des;ehaahc9-gh97i:des;ahca-7ah97i:7des;}.agdeadac {idgaag-idiidb:9es iahgd;idgaag-gh97i:9es iahgd;idgaag-aasi:9es #9s9s9s;idgaag-ide:9es #9s9s" + + "9s;ahhhihahi9:7haaac;edhhihdc:hihdagia;ahai7:7sses;7-hcaas:9;ehaahc9:ses;ggghdg:edhciag;}.haaagi {ihgd9gdgca-gdadg:#g9gdga;sadhi:aasi;}haaagi.gghiaghh {sdci-hh7a:s.dab;ahai7:9cses;}.hbhaa {sdci-hh7a:d" + + "d%;sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;}.ihiaaaehgag {ahai7:7ses;}.iasihcegi {ehaahc9:des;bhg9hc:ses;aagihgha-hah9c:ide;}i7 {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-aah97i:idaa;" + + "sdci-hh7a:dd%;}.ihiaa {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:9ab;sdci-aah97i:idaa;}.ahaga {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:9ab;}.aaaaafiddaihg {gaahg:idi7;a" + + "hai7:hgid;7ah97i:gces;ihgd9gdgca-gdadg:#d77ahc;}.iddaihgfaasi {sadhi:aasi;ahai7:hgid;iasi-hah9c:gaciag;}.iddaihgfgh97i {sadhi:gh97i;ahai7:hgid;iasi-hah9c:gaciag;}aha.iddaihg {sadhi:aasi;}.iddaihgfid9f" + + "aasi {sadhi:aasi;}.iddaihgfid9 {gdadg:#ssssss;sadhi:aasi;}.iddaihgfid9fgh97i {sadhi:aasi;}.iddaihgfigiidc {sadhi:aasi;iasi-hah9c:gaciag;ggghdg:edhciag;ehaahc9:ces ses ces ses;bhg9hc:ses;idgaag:ses cdc" + + "a;ihgd9gdgca-gdadg:ighchehgaci;}.iddaihgfigiidc.hchgihaa {ggghdg:hgid;}.iddaihgfigiidcfhgdc {gaahg:gh97i;sadhi:cdca;bhg9hc-aasi:hgid;bhg9hc-gh97i:hgid;idgaag:ses;}.iddaihgfigiidc .igiidcfiasi,.iddaihg" + + "figiidc hehc {ahheah9:iadgd;sdci-hh7a:s.cab;ehaahc9-aasi:des;ehaahc9-gh97i:des;sdci-shbha9:aghha,aaaaaihgh,hhch-haghs;ahca-7ah97i:s.cab;iasi-aagdghihdc:cdca;}.iddaihgfigiidc.bdghadaag .igiidcfiasi {gd" + + "adg:#ggbggg;}.iddaihgfigiidc.hchgihaa .igiidcfiasi {gdadg:9gh9;}.iicfahih9dcigdafaasi {sadhi:aasi;ehaahc9-aasi:des;}.cdaghe,.cdaghe ia,.iaehiaa {a7hia-hehga:cdaghe;}ihiaa.cdaghe,ihiaa.cdaghe ig,ia.ag" + + "he {a7hia-hehga:cdgbha;}.ihifhbhaafaasi {sadhi:aasi;}.ihifhbhaafgh97i {sadhi:aasi;}.ihifhbhaafid9 {sadhi:aasi;sdci-hh7a:dd%;}.ihifhbhaafid9 h {ahca-7ah97i:7ses;ehaahc9-ide:7es;}.ihifhbhaafdssfaasi {" + + "sadhi:aasi;ahai7:7ses;ehaahc9-ide:7es;}.ihifhbhaafdssfgh97i {sadhi:aasi;ehaahc9-ide:7es;}.ihifhbhaafdssfid9 {sadhi:aasi;sdci-hh7a:dd%;ehaahc9-ide:7es;}aha.ihifhbhaa {idgaag-aasi:9es hdaha #7a77hh;id" + + "gaag-gh97i:9es hdaha #7a77hh;idgaag-idiidb:9es hdaha #7a77hh;}aha.aaaaa9 {ahai7:hgid;gaahg:idi7;}aha.aaaaa9faasi {sadhi:aasi;ehaahc9-aasi:des;ehaahc9-ide:7es;ehaahc9-idiidb:7es;7ah97i:9ges;}aha.aaaaa" + + "9fgh97i {sadhi:gh97i;ehaahc9-aasi:des;ehaahc9-ide:7es;ehaahc9-idiidb:7es;7ah97i:9ges;}.aaaaa9fiasi {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;gdadg:#gggggg;sdci-hh7a:dd%;ehaahc9-aasi:7es;ehaahc9-g" + + "h97i:7es;}.aaaaa9fad9hc {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;gdadg:#gggggg;sdci-hh7a:dd%;sdci-aah97i:idaa;ehaahc9-aasi:des;ehaahc9-gh97i:des;}.aaaaa9fad9dgi {bhg9hc-gh97i:7es;}aha.ad9d {sad" + + "hi:aasi;}.aaaaa7fiasi {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;gdadg:#i7i7i7;sdci-aah97i:idaa;ehaahc9-aasi:des;ehaahc9-gh97i:des;ahca-7ah97i:hhes;}.aaaaafadghihdc {gaahg:idi7;ahai7:hgid;7ah97i:7" + + "ces;ihgd9gdgca-gdadg:#d77ahc;edhhihdc:gaahihaa;}.adghihdcfaasi {bhg9hc-aasi:hes;sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:9ab;}.aaaaafigahagggbi {gaahg:idi7;ahai7:hgid;7ah97i:7ces;ihgd9g" + + "dgca-gdadg:#d77ahc;}.igahagggbifaasi {ahai7:hgid;bhg9hc-gh97i:7dses;}.igahagggbifgh97i {sadhi:gh97i;ahai7:hgid;ahai7:7gces;}.igahagggbifigiidc {sadhi:aasi;bhg9hc-aasi:ses;bhg9hc-gh97i:ses;bhg9hc-ide:9" + + "es;ggghdg:edhciag;7ah97i:7ces;ihgd9gdgca-gdadg:ighchehgaci;idgaag:cdca;ehaahc9:ses;}.igahagggbifhbh9a {bhg9hc-aasi:hes;bhg9hc-gh97i:hes;bhg9hc-ide:9es;ggghdg:edhciag;7ah97i:7ces;idgaag:cdca;}.igahaggg" + + "bifehi7fgdcihhcag {ihgd9gdgca-gdadg:a7hia;ahai7:hgid;ehaahc9-ide:ses;ehaahc9-idiidb:ses;}.igahagggbihcah {ahai7:bes;7ah97i:7ces;ihgd9gdgca-gdadg:#d77ahc;}.igahagggbifehi7figiidc {sadhi:aasi;bhg9hc-gh9" + + "7i:9es;bhg9hc-ide:9es;bhg9hc-aasi:ses;}.cdsdgaag {idgaag:cdca;}.igahagggbifehi7fahcd {idgaag:cdca;}.igahagggbifehi7 {sadhi:aasi;}.igahagggbifehi7faasi,.igahagggbifehi7fgh97i {sdci-shbha9:sh7dbh,aghha," + + "aaaaaihgh,hhch-haghs;sdci-hh7a:9ab;}.igahagggbifehi7fiasi {iasi-aagdghihdc:cdca;gdadg:#ssssss;sdci-hh7a:s.dab;ahca-7ah97i:7ges;bhg9hc-aasi:hes;}.hahgg7fehi7fgdcihhcag7 {sadhi:gh97i;ihgd9gdgca-gdadg:a7" + + "hia;gdadg:9gh9;ehaahc9-ide:ses;ehaahc9-idiidb:ses;7ah97i:7ges;idgaag:7es hdaha #d77ahc;}.hahgg7fhcegi7 {sadhi:aasi;ahai7:9gces;aagihgha-hah9c:bhaaaa;bhg9hc-aasi:9es;idgaag:7es hdaha a7hia;ihgd9gdgca-g" + + "dadg:a7hia;}#hahgg7iasi {sdci-shbha9:sh7dbh,dacaah,hhch-haghs;sdci-hh7a:9ab;}#hahgg7sddaihg {sdci-hh7a:9ab;}.hahgg7fehi7figiidc {sadhi:aasi;bhg9hc-aasi:ses;bhg9hc-ide:9es;bhg9hc-gh97i:9es;ggghdg:edhci" + + "ag;idgaag:cdca;}.ahgagidg9fehi7 {sadhi:aasi;}aha.ehi7faasi,aha.ehi7fgh97i {sadhi:aasi;sdci-aah97i:cdgbha;bhg9hc:ses;ahca-7ah97i:7des;ihgd9gdgca-gdadg:#d77ahc;}aha.ggggacifehi7figiidch {ihgd9gdgca-gd" + + "adg:#d77ahc;sadhi:aasi;}aha.idddbhgdh {sadhi:aasi;ihgd9gdgca-gdadg:#d77ahc;7ah97i:7des;ahca-7ah97i:7des;}ga.idddbhgd {ihgd9gdgca-gdadg:#gsg9g9;}ah.idddbhgd {ahhi-hi9aa:cdca;ehaahc9-aasi:des;ehaahc9-id" + + "e:7es;ehaahc9-idiidb:ses;ehaahc9-gh97i:des;sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:s.dab;}ah.bhch9afidddbhgdh {ahhi-hi9aa:cdca;ehaahc9-aasi:des;ehaahc9-ide:7es;ehaahc9-idiidb:ses;ehaah" + + "c9-gh97i:des;sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;idgaag-ide:9es hdaha #7d7d7d;sdci-hh7a:s.dab;}aha.hahgg7 {sadhi:aasi;ahai7:hgid;ihgd9gdgca-gdadg:#d77ahc;}aha.9dd9aafhahgg7 {sadhi:gh97i;ihg" + + "d9gdgca-gdadg:#d77ahc;}aha.hahgg7fids {sdci-aah97i:cdgbha;bhg9hc:ses;ahca-7ah97i:7des;sadhi:aasi;ihgd9gdgca-gdadg:#d77ahc;}.bacg {idgaag-idiidb:9es hdaha #cscscs;idgaag-gh97i:9es hdaha #cscscs;idgaag" + + "-ide:9es hdaha #gggggg;idgaag-aasi:9es hdaha #gggggg;ihgd9gdgca-gdadg:#gggggg;sdci-shbha9:aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:s.dab;ehaahc9:ses;}aha.shaafbhch9ag {ehaahc9:ses;ihgd9gdgca-gdadg:#s7sca" + + "7;idgaag:9es hdaha #sgsgsg;ahai7:9ss%;sadhi:aasi;}aha.sddiag {ahai7:hgid;sadhi:gh97i;ehaahc9-ide:7es;ehaahc9-idiidb:ses;}aha.bahhh9a {sadhi:aasi;sdci-aah97i:cdgbha;sdci-hh7a:dd%;gdadg:iahgd;bhg9hc:se" + + "s;ahca-7ah97i:77es;ahai7:hgid;ehaahc9:7es;}.gh97ifgahgd {idgaag:9es hdaha #9s9s9s;ihgd9gdgca-gdadg:#gsgggg;sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:s.dab;ahhhihahi9:7haaac;edhhihdc:hihd" + + "agia;ahai7:97ses;7-hcaas:9;ehaahc9:9ses;ggghdg:edhciag;}aha.ehgdehhi {ihgd9gdgca-gdadg:#gggggg;idgaag:9es hdaha #b99h7h;ehaahc9:des;}aha.ehgdehhiagah {gaahg:idi7;ehaahc9:9ses;}.bhch9afeh9a {ihgd9gdgca" + + "-gdadg:#ghghgh;}.gdbbaci {edhhihdc:gaahihaa;bhg9hc-ide:7es;ehaahc9:9ses;gaahg:gh97i;}.aaac9dbbaci {ihgd9gdgca:#gggggg;}.daa9dbbaci {ihgd9gdgca-gdadg:#ghghgh;idgaag-ide:#sgsgsg;idgaag-idiidb:#sgsgsg;}." + + "haa9dbbaciagah {bhg9hc-idiidb:des;}.gdbbaciagi7dg {sdci-hh7a:9ab;sdci-aah97i:idaa;ehaahc9-idiidb:des;}.gdbbacisasi {sdci-hh7a:dd%;ehaahc9-idiidb:9es;}.gdbbacishbahihbe {sdci-hh7a:s.cab;}.gdbbaci7aaaia" + + "97agdids {edhhihdc:hihdagia;gh97i:9ses;ide:9ses;ggghdg:edhciag;}aha.eh9afihiaa {gaahg:idi7;ahai7:hgid;}aha.hhaafchah9hihdc {bhg9hc-ide:ses;}ia.hhaafchah9hihdc {ihgd9gdgca-hbh9a:gga(hi9aah/hhaafchafid" + + "9.ge9);ihgd9gdgca-gaeahi:gaeahi-s;ahai7:9bdes;idgaag-gh97i:9es hdaha #sgsgsg;}.ihiehhi {bhg9hc-aasi:7ses;bhg9hc-ide:7ses;bhg9hc-idiidb:7ses;ehaahc9-aasi:9bes;}.ihiehhi igiidc {ihgd9gdgca-gdadg:ighcheh" + + "gaci;idgaag:ses cdca;bhg9hc:ses;ehaahc9:ses;ggghdg:edhciag;sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:9ab;iasi-hah9c:aasi;}.ihiehhi ah {ehaahc9-aasi:ses;ehaahc9-ide:ses;ehaahc9-gh97i:ses;" + + "ehaahc9-idiidb:ges;ahhi-hi9aa-i9ea:cdca;sdci-hh7a:s.dab;}.aaihfhiabfhgihaa {ahhi-hi9aa-hbh9a:gga(hbh9ah/haaagiaafhgihdc.9hs);}.aaihfhiabfhgihaa igiidc {sdci-aah97i:idaa;}aha.gh97ifeh9afgdciaci {ahai7" + + ":hgid;ihgd9gdgca-gdadg:a7hia;}aha.hbhaafhgifiddaihg {ihgd9gdgca-gdadg:#9s9s9s;7ah97i:7des;ehaahc9-idiidb:des;}aha.hgifigiidc {sdci-hh7a:s.dab;ehaahc9-aasi:7es;ehaahc9-gh97i:7es;sadhi:aasi;iasi-hah9c:" + + "gaciag;}aha.eh9afihiaa {bhg9hc:ses;}.eh9afihiaa hcegi,.eh9afihiaa hgibhi,.eh9afihiaa haaagi,.eh9afihiaa iasihgah,.sdgbfgdciaci iasihgah {sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:9ab;}a" + + "ha.eh9afahih {bhg9hc:9ses;sadhi:aasi;}aha.bdahafsdgb {idgaag:9es hdaha #a9a9a9;bhc-ahai7:gases;}aha.sdgbfgdciaci {bhg9hc:7as;gaahg:idi7;ahai7:hgid;}.sdgbfgdciaci h {sdci-hh7a:9ab;}aha.sdgbfihiaafihg" + + " {idgaag-aasi:9es hdaha #b99h7h;idgaag-gh97i:9es hdaha #b99h7h;gaahg:idi7;}.sdgbfihiaafiasi {ahca-7ah97i:7aes;sdci-shbha9:sh7dbh,aghha,aaaaaihgh,hhch-haghs;sdci-hh7a:9ab;sdci-aah97i:idaa;gdadg:#hhhh" + + "hh;sdci-aah97i:idaa;aagihgha-hah9c:ihhaahca;bhg9hc:ses;ehaahc9-aasi:des;}.iicfeh9a9dcigdafaasi {ehaahc9-ide:ges;sadhi:aasi;}.iicfeh9a9dcigdafgh97i {ehaahc9-ide:ges;sadhi:gh97i;}aha.sdgbfgdcigdah {gaa" + + "hg:idi7;}aha.ghahdfigiidch {ehaahc9:des;}.aaig9sdgaag {idgaag:9es hdaha gaa;}.aaig9 {idgaag:9es hdaha #iii;}.haahcgaafhahgg7 {ahai7:hgid;gaahg:idi7;bhg9hc:9ses;ihgd9gdgca-gdadg:#ac7ahs;edhhihdc:gaah" + + "ihaa;}aha.hahgg7fid9 {edhhihdc:gaahihaa;ihgd9gdgca-gdadg:#h9h9h9;idgaag-idiidb:7es hdaha #d9a9sb;}.hahgg7fgdcihhcag {ahai7:hgid;bhg9hc-aasi:7ses;ehaahc9-ide:9ses;}aha.hahgg7fgefaasifgdgcag {sadhi:aa" + + "si;}aha.hahgg7fgefgh97ifgdgcag {sadhi:gh97i;}.hahgg7fihiaa {sdci-hh7a:dd%;sdci-aah97i:idaa;ehaahc9-aasi:des;ehaahc9-ide:des;}.hahgg7fgghiaghh {ehaahc9-aasi:9ses;ehaahc9-gh97i:ses;ehaahc9-ide:des;sdci-" + + "hh7a:dd%;ihgd9gdgca-gdadg:#gggggg;idgaag:9es hdaha #sgsgsg;}aha.hahgg7figiidcfgda {ehaahc9-ide:des;idgaag-ide:9es hdaha #sgsgsg;sadhi:aasi;ahai7:9ss%;}aha.hahgg7figiidcfgdcihhcag {sadhi:gh97i;}aha.hah" + + "gg7fhaa9ghiaghhfgda {idgaag-idiidb:9es hdaha #sgsgsg;sadhi:aasi;ahai7:9ss%;7ah97i:7des;}aha.hahgg7fgghiaghhfgdcihhcag {idgaag:7es hdaha #d9a9sb;ehaahc9:des;}.hgdcehiaafiasi {sadhi:aasi;bhg9hc:ses;7ah9" + + "7i:7des;ahca-7ah97i:7des;ehaahc9-gh97i:des;sdci-hh7a:dd%;sdci-aah97i:idaa;}aha.hgdc {ahai7:7des;7ah97i:7des;sadhi:aasi;}a:ahcd.hahgg79ghiaghhfiasi {iasi-aagdghihdc:gcaagahca;gdadg:#ssssss;sdci-hh7a" + + "{sadhi:aasi;ahhi-hi9aa:cdca;bhg9hc-ide:hes;}.7aae9dcihhcag {edhhihdc:hihdagia;ide:7es;gh97i:7es;bhg9hc:ses;ehaahc9:ses;idgaag:ses cdca;7-hcaas:g;ggghdg:edhciag;ahhhihahi9:hc7aghi;ihgd9gdgca-gdadg:ighc" + + "hehgaci;}.7aae9dcihhcagaihihg {bhg9hc:ses;ehaahc9:ses;ggghdg:edhciag;ahhhihahi9:hc7aghi;}.9gh-bdagaa .7aae9dcihhcag {gh97i:ges;}.9gh-ahhad9 .9gh-ehcaa .7aae9dcihhcag {gh97i:7ses;}.sdgb7hih {sdci-hh7a" + + ":9ab;}ihiaa.cdihs9shiaa {idgaag:ses;}ihiaa.cdihs9shiaa .aasi9dagbc {iasi-hah9c:gh97i;aagihgha-hah9c:ide;sdci-aah97i:idaa;}ihiaa.cdihs9shiaa .gh97i9dagbc {iasi-hah9c:aasi;}ihiaa.hgeagihiaa {bhg9hc:s s " + + "s 7es;}ihiaa.hgeagihiaa iida9 ig ia.geadhaf7ahaag {idgaag-ide:9es hdaha g9i(9g7,9g7,9g7);idgaag-gh97i:9es hdaha g9i(9g7,9g7,9g7);idgaag-idiidb:cdca;idgaag-aasi:9es hdaha g9i(9g7,9g7,9g7);ihgd9gdgca:g9" + + "i(77d,77d,77d);ehaahc9:s;sdci-hh7a:s;7ah97i:hses;}ihiaa.hgeagihiaa iida9 ig ia.geadhaf7ahaag ihiaa {sdci-aah97i:ass;sdci-hh7a:9ab;ihgd9gdgca:ighchehgaci;gdadg:iahgd;ahai7:9ss%;bhg9hc:s;}ihiaa.hgeagihi" + + "aa iida9 ig ia.gdcihhchfgeadha {bhg9hc:s;ehaahc9-aasi:s;}aha.geadha {bhg9hc:s;ehaahc9:s;daagsada:hgid;7ah97i:7hses;ahai7:asses;idgaag:9es hdaha g9i(9g7,9g7,9g7);ihgd9gdgca:a7hia;}aha.geadha ihiaa {7ah" + + "97i:9ss%;idgaag:cdca;}.geadha-daagaghia {}aha.geadha ihiaa iida9 ig {7ah97i:7des;}aha.geadha ihiaa iida9 ig#geadhafiahcdgda {7ah97i:hgid;}.geadhafsddiag {idgaag-ide:cdca;idgaag-gh97i:9es hdaha g9i(9g7" + + ",9g7,9g7);idgaag-idiidb:9es hdaha g9i(9g7,9g7,9g7);idgaag-aasi:9es hdaha g9i(9g7,9g7,9g7);7ah97i:hses;ihgd9gdgca:g9i(77d,77d,77d);}.iaehiaa {sdci-aah97i:idaa;}.ihiaaf7ahaag {sdci-aah97i:idaa;sdci-hh7a" + + ":9ab;gdadg:#ssssss;ahca-7ah97i:7hes;ahai7:9ss%;}.gh97ifihiaaf7ahaag {ahca-7ah97i:7hes;ahai7:hes;sdci-aah97i:idaa;gdadg:#ssssss;sdci-hh7a:9ab;}.aasifihiaaf7ahaag {ahca-7ah97i:7hes;ahai7:ges;sdci-aah97i" + + "{ihgd9gdgca-gdadg:#gggggg;ia77bgd-abdas:ses;ia77bgd-ehgs:ses;ia77bgd-shsshf:ses;ia77bgd-shi:hces;faadbg-abdas:des;faadbg-ehgs:des;faadbg-shi:hces;faadbg-shsshf:ses;ghgs-abah:9ss%;}.aabhcfhaiahiaa {" + + "sdci-hh7a:9ab;}#shaahgdgb {edhhihdc:hihdagia;ide:ses;aasi:ses;ahai7:ses;7ah97i:ses;}.aaaaafhgiihih {ihgd9gdgca-gdadg:#gggggg;7ah97i:7ses;}.gdagbcf7ahaagfadc9 {sdci-hh7a:dd%;7ah97i:hses;gdadg:#sssss" + + "s;}aha.ihifhaafeagbhhhhdch,aha.ihifihhhgfeagbhhhhdch {sadhi:aasi;ahai7:hgid;iasi-hah9c:gaciag;}aha.eagbhhhhdchbhhaehhi {bhg9hc:ses;ehaahc9-aasi:7ses;ehaahc9-ide:des;ehaahc9-idiidb:des;}aha.eagbhhhhdch" + + "bhhaehhi ga {bhg9hc:ses;ehaahc9:ses ses ses 9ses;}aha.hgdcehiaafiasi {sadhi:aasi;bhg9hc:ses;7ah97i:7des;ahca-7ah97i:7des;ehaahc9-gh97i:des;sdci-aah97i:idaa;}aha.hgdc {ahai7:7des;7ah97i:7des;sadhi:a" + + "asi;}a.ahia {gdadg:#ggssss;sdci-aah97i:cdgbha;}a:ahhhiaa.ahia {gdadg:#ggssss;sdci-aah97i:cdgbha;}a:7daag.ahia {gdadg:#ggssss;sdci-aah97i:cdgbha;}a:hgihaa.ahia {gdadg:#ggssss;sdci-aah97i:cdgbha;}.ahia" + + "{9heha:#ggssss;ghgs-hhbdas:idaa;}.gh97ifiddaihg {sadhi:gh97i;ehaahc9-gh97i:des;}aha.asfhahgg7fid9 {bhg9hc-aasi:hes;bhg9hc-gh97i:hes;bhg9hc-idiidb:9ses;idgaag-idiidb:7es hdaha #d9a9sb;}.asfhahgg7 {ah" + + "ai7:hgid;gaahg:idi7;7ah97i:9hses;ehaahc9-ide:ses;bhg9hc:ses;ihgd9gdgca-gdadg:#d77ahc;}aha.asfhahgg7faasiaca {sadhi:aasi;}aha.asfhahgg7fgh97iaca {sadhi:gh97i;}.ahih h {sdci-hh7a:9ab;}.asfhahgg7fgdcihh" + + "cag {sadhi:aasi;bhg9hc-ide:des;}ia.asfhahgg7fiasi {sdci-hh7a:s.dab;}.asfhahgg7fiasi hcegi {sdci-hh7a:s.dab;}.asfhahgg7fiasi haaagi {sdci-hh7a:s.dab;}ia.asfihiaaaehgag {ahai7:9ses;}.asfihiaa9aaa {ehaah" + + "c9-aasi:9ses;ehaahc9-gh97i:des;}aha.hgihdcfhiae {ehaahc9-ide:7es;ehaahc9-idiidb:7es;ehaahc9-aasi:des;ehaahc9-gh97i:9ses;bhg9hc-gh97i:9des;ihgd9gdgca-gdadg:a7hia;idgaag-idiidb:7es dgihai;idgaag-gh97i:7" + + "es dgihai;ahai7:dd%;}aha.aaghhhdcfhiae {ehaahc9-ide:7es;ehaahc9-idiidb:7es;ehaahc9-aasi:des;ehaahc9-gh97i:des;ihgd9gdgca-gdadg:a7hia;idgaag-idiidb:7es dgihai;idgaag-gh97i:7es dgihai;ahai7:dd%;}aha.i7" + + "ac {ihgd9gdgca:#hhgggh;ehaahc9-ide:7es;ehaahc9-idiidb:7es;ehaahc9-aasi:7es;ehaahc9-gh97i:des;idgaag-idiidb:7es dgihai;idgaag-gh97i:7es dgihai;ahai7:dd%;}aha.i7acfida9 {ehaahc9-ide:des;ehaahc9-idiidb:" + + "des;ehaahc9-aasi:9ses;ehaahc9-gh97i:des;ahai7:dd%;}aha.aaha {ihgd9gdgca:#hhgggh;ehaahc9-ide:7es;ehaahc9-idiidb:7es;ehaahc9-aasi:7es;ehaahc9-gh97i:des;idgaag-idiidb:7es dgihai;idgaag-gh97i:7es dgihai;a" + + "hai7:dd%;}aha.aahafida9 {ehaahc9-ide:des;ehaahc9-idiidb:des;ehaahc9-aasi:9ses;ehaahc9-gh97i:des;ahai7:dd%;}aha.haafhiae {ehaahc9-ide:9ses;ehaahc9-idiidb:9ses;iasi-hah9c:gaciag;}aha.casifhiae {ihgd9gd" + + "gca:#hhgggh;ehaahc9-ide:7es;ehaahc9-idiidb:7es;ehaahc9-aasi:7es;ehaahc9-gh97i:des;idgaag-idiidb:7es dgihai;idgaag-gh97i:7es dgihai;ahai7:dd%;}aha.hsfida9 {ehaahc9-ide:des;ehaahc9-idiidb:des;ehaahc9-aa" + + "si:9ses;ehaahc9-gh97i:des;ahai7:dd%;}aha.ehghbaiagh {ehaahc9-ide:des;ehaahc9-idiidb:des;ehaahc9-aasi:9ses;ehaahc9-gh97i:des;ahai7:dd%;}aha.eh9afida9 {ehaahc9-ide:9ses;ehaahc9-aasi:9ses;}aha.h7dgifhgi" + + "hdcfchba {sdci-hh7a:9ss%;}aha.h7dgifaaghhhdcfchba {sdci-hh7a:9ss%;}.gahaagfcdihshghihdc {ahheah9:cdca;}.gahaaghaabaci {edhhihdc:hihdagia;ide:-9es;aasi:-9ses;7ah97i:9es;ahca-7ah97i:9es;idgaag:cdca;eh" + + "aahc9:ses;bhg9hc:ses;ahai7:9es;daagsada:7haaac;}ihiaa.dig i7 aha {edhhihdc:gaahihaa;ide:-9ses;7ah97i:9es;daagsada:7haaac;}.idaaehiaah ahiaa,.ahiaa {sdci-aah97i:idaa;sdci-hh7a:9ab;}shaaahai.ghahddgdge " + + "{ehaahc9:ses;idgaag:cdca;iasi-hah9c:aasi;}shaaahai.ghahddgdge aa9aca {ehaahc9:ses;}shaaahai.ghahddgdge aha {ehaahc9-aasi:7ses;ehaahc9-idiidb:des;}shaaahai.ghahddgdge ahiaa {ahheah9:iadgd;bhg9hc-aasi:9" + + "79.ihiaa,77.ihiaa {bhg9hc:ses;}.9gh-bdagaa,.ahdhfihi7haa9dciaci,.hahgg7fid9,.haahcgaafhahgg7 {7ddb:9;}.eghcghehaehhi {ahhi-hi9aa-i9ea:cdca;}.ihiehhi igiidc,.aaaaa7fbacg igiidc,.aaaaahfbacg igiidc,.ihh" + + "hgsgiidc,.iddaihgfigiidc,.igahagggbihfahgchba hcegi,.shaaa9hiabsgdahagsgaa hcegi {ahai7:hgid;daagsada:ahhhiaa;}.gbaiaeaahahc9h {ehaahc9-aasi:7ses;sdci-aah97i:idaa;sdci-hh7a:9ss%;}.9ghaids {ahai7:9ss%;" + + "sdci-shbha9:sh7dbh,dacaah,hhch-haghs;sdci-hh7a:9ss%;ehaahc9:ses;bhg9hc:ses;idgaag-idiidb:ses;idgaag-aasi:ses;idgaag-gh97i:ses;ihgd9gdgca-gdadg:#gshghg;daagsada:7haaac;}.digids hcegi {ggghdg:aashgai;}." + + "bgaihehhi {sadhi:aasi;ahheah9:hcahca;ehaahc9:s 7ses;bhg9hc:s;}.bgaihehhi .cd7hagah {ahhi-hi9aa:cdca;}.s9fgaciagidhhihdc {bhg9hc:ses hgid;}.ahdhfeh9a9dciaci {ihgd9gdgca-hbh9a:gga(hi9aah/ahdhfid9.ge9);i" + + "hgd9gdgca-gaeahi:gaeahi-9;}shca9haghaaaaagihdc,.asfhahgg7fid9,#aaaaa7,.aaaaa7fihifshaafaasi,.iddaihg,.iddaihgfid9,.aaaaa7fihih,.aaaaa7fbacg .ihifbha,.aaaaa7fihifshaafgh97i,aha.aaaaa7fhgihaa .ihifbha,." + + "ahdhfaaaaa9,.gdagbcf7ahaagfadc9,.ahdhfeh9ashiaa,.ahdhfeh9agddiag,.igahagggbifehi7fgdcihhcag,.ggggacifehi7figiidch,.hahgg7fids,#aaaaah,.aaaaahfchah9hihdc,.aaaaahfhgihaa .ihifbha,.ahdhfihi .ihisd9,.ahdh" + + "fihi .haaagiaa .ihisd9,.ahdhfihighaa,#aaaaa9,.aaaaa9,aha.9ghaids .s7ag,aha.9ghaids .sig,aha.9ghaids .sig ia,.gdagbcf7ahaag,.ihifhbhaafid9,.ihifhbhaafdssfid9,.eagbfihifhaaagiaa .ihifhbhaafid9,.ahdhfaaa" + + "aah {ihgd9gdgca-hbh9a:gga(heghiahfgfd.ec9);ihgd9gdgca-gaeahi:gaeahi-s;}shca9haghaaaaagihdc {ihgd9gdgca-edhhihdc:-ses -ses;7ah97i:9gces;}.asfhahgg7fid9 {ihgd9gdgca-edhhihdc:-ses -9gces;7ah97i:97ses;}#a" + + "aaaa7,.aaaaa7fihifshaafaasi {ihgd9gdgca-edhhihdc:-ses -7aces;7ah97i:gces;}.iddaihg,.iddaihgfid9 {ihgd9gdgca-edhhihdc:-ses -h9aes;7ah97i:gces;}a.aaaaa7fihih,.aaaaa7fbacg .ihifbha,.aaaaa7fihifshaafgh97i" + + " {ihgd9gdgca-edhhihdc:-ses -hages;7ah97i:hhes;}aha.aaaaa7fhgihaa .ihifbha {ihgd9gdgca-edhhihdc:-ses -hdbes;7ah97i:hhes;}.ahdhfaaaaa9 {ihgd9gdgca-edhhihdc:-ses -ghses;7ah97i:h7es;}.gdagbcf7ahaagfadc9 {" + + "ihgd9gdgca-edhhihdc:-ses -ga7es;7ah97i:hses;}.ahdhfeh9ashiaa {ihgd9gdgca-edhhihdc:-ses -gd7es;7ah97i:7des;}.hahgg7fgefgh97ifgdgcag {ihgd9gdgca-edhhihdc:-h9es -99ges;ahai7:9ses;7ah97i:ces;}.hahgg7fid9 " + + "{ihgd9gdgca-hbh9a:gga(hi9aah/haahcgaafehcaafid9.ge9);ihgd9gdgca-gaeahi:gaeahi-s;}.eh9afihiaa,aha.eh9afgdcigdah,aha.sdgbfgdcigdah,aha.9gh-ahhad9 aha.9gh-bdagaa aha.si,aha.gh97ifeh9afgdciaci aha.9gh-bda" + + "gaa aha.7a,aha.ggahiahh7hga9dciaciagah aha.9gh-bdagaa aha.si,aha.cdihs99dciaciagah aha.9gh-bdagaa aha.si,aha.agdesdshh7hga9dciaciagah aha.9gh-bdagaa aha.si,.sdgbfihiaafihg,aha.9gh-ahhad9 aha.9gh-bdaga" + + "a aha.7a,.hcaggaacfihiaashg,.ihiaaf7ahaag,.gh97ifihiaaf7ahaag,.aasifihiaaf7ahaag,.igiidcfid9,.hchgihaa .igiidcfid9 {ihgd9gdgca-hbh9a:gga(ihhaaeghiahfgfd.ec9);ihgd9gdgca-gaeahi:gaeahi-s;}.eh9afihiaa,ah" + + "a.eh9afgdcigdah,aha.sdgbfgdcigdah,aha.9gh-ahhad9 aha.9gh-bdagaa aha.si,aha.gh97ifeh9afgdciaci aha.9gh-bdagaa aha.7a,aha.ggahiahh7hga9dciaciagah aha.9gh-bdagaa aha.si,aha.cdihs99dciaciagah aha.9gh-bdag" + + "aa aha.si,aha.agdesdshh7hga9dciaciagah aha.9gh-bdagaa aha.si {ihgd9gdgca-edhhihdc:-ses -ses;7ah97i:7des;}.sdgbfihiaafihg,aha.9gh-ahhad9 aha.9gh-bdagaa aha.7a,.hcaggaacfihiaashg,.ihiaaf7ahaag,.gh97ifih" + + "iaaf7ahaag,.aasifihiaaf7ahaag {ihgd9gdgca-edhhihdc:-ses -7des;7ah97i:7aes;}.igiidcfid9 {edhhihdc:gaahihaa;bhg9hc-gh97i:7es;ehaahc9-gh97i:9hes;ehaahc9-aasi:9hes;bhg9hc-aasi:7es;a7hia-hehga:cdaghe;ihgd9" + + "gdgca-edhhihdc:-ses -ddes;7ah97i:77es;}.hchgihaa .igiidcfid9 {ihgd9gdgca-edhhihdc:-ses -bbes;7ah97i:77es;}.aashgaifad9d,.hchgihaa .igiidcfaasi,.igiidcfaasi,.igiidcfgh97i,.hchgihaa .igiidcfgh97i,.hgdcf" + + "hcsd,.hgdcfaggdg {ihgd9gdgca-hbh9a:gga(ihhaaeghiahfghgh.ec9);ihgd9gdgca-gaeahi:cd-gaeahi;}.aashgaifad9d {ihgd9gdgca-edhhihdc:-ses -ses;ahai7:c7es;7ah97i:hdes;}.hchgihaa .igiidcfaasi {ihgd9gdgca-edhhih" + + "dc:-c7es -ses;ahai7:9ges;7ah97i:77es;}.igiidcfaasi {edhhihdc:hihdagia;ide:ses;aasi:-7es;ihgd9gdgca-edhhihdc:-daes -ses;ahai7:9hes;7ah97i:77es;}.igiidcfgh97i {edhhihdc:hihdagia;ide:ses;gh97i:-7es;ihgd9" + + "gdgca-edhhihdc:-9sdes -ses;ahai7:9hes;7ah97i:77es;}.hchgihaa .igiidcfgh97i {ihgd9gdgca-edhhihdc:-977es -ses;ahai7:9hes;7ah97i:77es;}.hgdcfhcsd {ihgd9gdgca-edhhihdc:-c7es -77es;ahai7:9ges;7ah97i:9des;}" + + ".hgdcfaggdg {ihgd9gdgca-edhhihdc:-daes -77es;ahai7:9ges;7ah97i:9ges;}"; + + var largeNewValue = largeTest, + len = largeTest.length, + count = nextRandom() % 20, + removeBound = len-(count*100), + logData = []; + for (; count > 0; count--) { + var removePos = nextRandom() % removeBound; + var removeLength = 1+nextRandom()%100; + logData.push("(" + removePos + ", " + removeLength + ")"); + largeNewValue = largeNewValue.substring(0, removePos) + + largeNewValue.substring(removePos + removeLength); + } + log("len: " + len + " count: " + count + " removed ( " + logData.join(", ") + " )"); + + diffResult = diff.diffWords(largeTest, largeNewValue); + log("diffResult length: " + diffResult.length); + var removeCount = 0; + var removeChanges = [], addChanges = [], testChanges = []; + for (var i = 0; i < diffResult.length; i++) { + if (diffResult[i].removed) { + log("remove Change " + i, diffResult[i]); + removeChanges.push(diffResult[i].value); + } else if (diffResult[i].added) { + log("add Change " + i, diffResult[i]); + addChanges.push(diffResult[i].value); + } else { + log("no Change " + i, diffResult[i]); + removeChanges.push(diffResult[i].value); + addChanges.push(diffResult[i].value); + } + } + + log("diffResult remove length: " + removeCount); + assert.equal(largeTest.replace(/s+/g, ""), removeChanges.join("").replace(/s+/g, ""), "New Diff results match"); + assert.equal(largeNewValue.replace(/s+/g, ""), addChanges.join("").replace(/s+/g, ""), "Old Diff results match"); +}; + +exports['Patch'] = function() { + // Create patch + var oldFile = + "value\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "remove value\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "remove value\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "value\n" + + "context\n" + + "context"; + var newFile = + "new value\n" + + "new value 2\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "add value\n" + + "context\n" + + "context\n" + + "context\n" + + "context\n" + + "new value\n" + + "new value 2\n" + + "context\n" + + "context"; + var expectedResult = + "Index: testFileName\n" + + "===================================================================\n" + + "--- testFileName\tOld Header\n" + + "+++ testFileName\tNew Header\n" + + "@@ -1,5 +1,6 @@\n" + + "+new value\n" + + "+new value 2\n" + + "-value\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + "@@ -7,9 +8,8 @@\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + "-remove value\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + "@@ -17,20 +17,21 @@\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + "-remove value\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + "+add value\n" + + " context\n" + + " context\n" + + " context\n" + + " context\n" + + "+new value\n" + + "+new value 2\n" + + "-value\n" + + " context\n" + + " context\n" + + "\\ No newline at end of file\n"; + + diffResult = diff.createPatch("testFileName", oldFile, newFile, "Old Header", "New Header"); + assert.equal( + expectedResult, + diffResult); + + expectedResult = + "Index: testFileName\n" + + "===================================================================\n" + + "--- testFileName\tOld Header\n" + + "+++ testFileName\tNew Header\n"; + diffResult = diff.createPatch("testFileName", oldFile, oldFile, "Old Header", "New Header"); + assert.equal( + expectedResult, + diffResult, + "Patch same diffResult Value"); +}; diff --git a/node_modules/mocha/node_modules/growl/History.md b/node_modules/mocha/node_modules/growl/History.md new file mode 100644 index 0000000..7b1c5c2 --- /dev/null +++ b/node_modules/mocha/node_modules/growl/History.md @@ -0,0 +1,42 @@ + +1.5.0 / 2012-02-08 +================== + + * Added windows support [perfusorius] + +1.4.1 / 2011-12-28 +================== + + * Fixed: dont exit(). Closes #9 + +1.4.0 / 2011-12-17 +================== + + * Changed API: `growl.notify()` -> `growl()` + +1.3.0 / 2011-12-17 +================== + + * Added support for Ubuntu/Debian/Linux users [niftylettuce] + * Fixed: send notifications even if title not specified [alessioalex] + +1.2.0 / 2011-10-06 +================== + + * Add support for priority. + +1.1.0 / 2011-03-15 +================== + + * Added optional callbacks + * Added parsing of version + +1.0.1 / 2010-03-26 +================== + + * Fixed; sys.exec -> child_process.exec to support latest node + +1.0.0 / 2010-03-19 +================== + + * Initial release diff --git a/node_modules/mocha/node_modules/growl/Readme.md b/node_modules/mocha/node_modules/growl/Readme.md new file mode 100644 index 0000000..c40ecab --- /dev/null +++ b/node_modules/mocha/node_modules/growl/Readme.md @@ -0,0 +1,93 @@ +# Growl for nodejs + +Growl support for Nodejs. This is essentially a port of my [Ruby Growl Library](http://github.com/visionmedia/growl). Ubuntu/Linux support added thanks to [@niftylettuce](http://github.com/niftylettuce). You'll need [growlnotify(1)](http://growl.info/extras.php#growlnotify). + +## Installation + +### Mac OS X (Darwin): + + Install [npm](http://npmjs.org/) and run: + + $ npm install growl + +### Ubuntu (Linux): + + Install `notify-send` through the [libnotify-bin](http://packages.ubuntu.com/libnotify-bin) package: + + $ sudo apt-get install libnotify-bin + + Install [npm](http://npmjs.org/) and run: + + $ npm install growl + +### Windows: + + Download and install [Growl for Windows](http://www.growlforwindows.com/gfw/default.aspx) + + Download [growlnotify](http://www.growlforwindows.com/gfw/help/growlnotify.aspx) - **IMPORTANT :** Unpack growlnotify to a folder that is present in your path! + + Install [npm](http://npmjs.org/) and run: + + $ npm install growl + +## Examples + +Callback functions are optional + + var growl = require('growl') + growl('You have mail!') + growl('5 new messages', { sticky: true }) + growl('5 new emails', { title: 'Email Client', image: 'Safari', sticky: true }) + growl('Message with title', { title: 'Title'}) + growl('Set priority', { priority: 2 }) + growl('Show Safari icon', { image: 'Safari' }) + growl('Show icon', { image: 'path/to/icon.icns' }) + growl('Show image', { image: 'path/to/my.image.png' }) + growl('Show png filesystem icon', { image: 'png' }) + growl('Show pdf filesystem icon', { image: 'article.pdf' }) + growl('Show pdf filesystem icon', { image: 'article.pdf' }, function(err){ + // ... notified + }) + +## Options + + - title + - notification title + - name + - application name + - priority + - priority for the notification (default is 0) + - sticky + - weither or not the notification should remainin until closed + - image + - Auto-detects the context: + - path to an icon sets --iconpath + - path to an image sets --image + - capitalized word sets --appIcon + - filename uses extname as --icon + - otherwise treated as --icon + +## License + +(The MIT License) + +Copyright (c) 2009 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. diff --git a/node_modules/mocha/node_modules/growl/lib/growl.js b/node_modules/mocha/node_modules/growl/lib/growl.js new file mode 100644 index 0000000..38a0c08 --- /dev/null +++ b/node_modules/mocha/node_modules/growl/lib/growl.js @@ -0,0 +1,188 @@ +// Growl - Copyright TJ Holowaychuk (MIT Licensed) + +/** + * Module dependencies. + */ + +var exec = require('child_process').exec + , path = require('path') + , os = require('os') + , cmd; + +switch(os.type()) { + case 'Darwin': + cmd = { + type: "Darwin" + , pkg: "growlnotify" + , msg: '-m' + , sticky: '--sticky' + , priority: { + cmd: '--priority' + , range: [ + -2 + , -1 + , 0 + , 1 + , 2 + , "Very Low" + , "Moderate" + , "Normal" + , "High" + , "Emergency" + ] + } + }; + break; + case 'Linux': + cmd = { + type: "Linux" + , pkg: "notify-send" + , msg: '' + , sticky: '-t 0' + , icon: '-i' + , priority: { + cmd: '-u' + , range: [ + "low" + , "normal" + , "critical" + ] + } + }; + break; + case 'Windows_NT': + cmd = { + type: "Windows" + , pkg: "growlnotify" + , msg: '' + , sticky: '/s:true' + , title: '/t:' + , icon: '/i:' + , priority: { + cmd: '/p:' + , range: [ + -2 + , -1 + , 0 + , 1 + , 2 + ] + } + }; + break; +} + +/** + * Expose `growl`. + */ + +exports = module.exports = growl; + +/** + * Node-growl version. + */ + +exports.version = '1.4.1' + +/** + * Send growl notification _msg_ with _options_. + * + * Options: + * + * - title Notification title + * - sticky Make the notification stick (defaults to false) + * - priority Specify an int or named key (default is 0) + * - name Application name (defaults to growlnotify) + * - image + * - path to an icon sets --iconpath + * - path to an image sets --image + * - capitalized word sets --appIcon + * - filename uses extname as --icon + * - otherwise treated as --icon + * + * Examples: + * + * growl('New email') + * growl('5 new emails', { title: 'Thunderbird' }) + * growl('Email sent', function(){ + * // ... notification sent + * }) + * + * @param {string} msg + * @param {object} options + * @param {function} fn + * @api public + */ + +function growl(msg, options, fn) { + var image + , args + , options = options || {} + , fn = fn || function(){}; + + // noop + if (!cmd) return fn(new Error('growl not supported on this platform')); + args = [cmd.pkg]; + + // image + if (image = options.image) { + switch(cmd.type) { + case 'Darwin': + var flag, ext = path.extname(image).substr(1) + flag = flag || ext == 'icns' && 'iconpath' + flag = flag || /^[A-Z]/.test(image) && 'appIcon' + flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image' + flag = flag || ext && (image = ext) && 'icon' + flag = flag || 'icon' + args.push('--' + flag, image) + break; + case 'Linux': + args.push(cmd.icon + image); + break; + case 'Windows': + args.push(cmd.icon + '"' + image.replace(/\\/g, "\\\\") + '"'); + break; + } + } + + // sticky + if (options.sticky) args.push(cmd.sticky); + + // priority + if (options.priority) { + var priority = options.priority + ''; + var checkindexOf = cmd.priority.range.indexOf(priority); + if (~cmd.priority.range.indexOf(priority)) { + args.push(cmd.priority, options.priority); + } + } + + // name + if (options.name && cmd.type === "Darwin") { + args.push('--name', options.name); + } + + switch(cmd.type) { + case 'Darwin': + args.push(cmd.msg); + args.push('"' + msg + '"'); + if (options.title) args.push(options.title); + break; + case 'Linux': + if (options.title) { + args.push("'" + options.title + "'"); + args.push(cmd.msg); + args.push("'" + msg + "'"); + } else { + args.push("'" + msg + "'"); + } + break; + case 'Windows': + args.push('"' + msg + '"'); + if (options.title) args.push(cmd.title + '"' + options.title + '"'); + break; + } + + // execute + exec(args.join(' '), fn); +}; diff --git a/node_modules/mocha/node_modules/growl/package.json b/node_modules/mocha/node_modules/growl/package.json new file mode 100644 index 0000000..2a5619f --- /dev/null +++ b/node_modules/mocha/node_modules/growl/package.json @@ -0,0 +1,22 @@ +{ + "name": "growl", + "version": "1.5.0", + "description": "Growl unobtrusive notifications", + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "main": "./lib/growl.js", + "_id": "growl@1.5.0", + "dependencies": {}, + "devDependencies": {}, + "optionalDependencies": {}, + "engines": { + "node": "*" + }, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "growl@1.5.x" +} diff --git a/node_modules/mocha/node_modules/growl/test.js b/node_modules/mocha/node_modules/growl/test.js new file mode 100644 index 0000000..2e75000 --- /dev/null +++ b/node_modules/mocha/node_modules/growl/test.js @@ -0,0 +1,16 @@ + +var growl = require('./lib/growl') + +growl('You have mail!') +growl('5 new messages', { sticky: true }) +growl('5 new emails', { title: 'Email Client', image: 'Safari', sticky: true }) +growl('Message with title', { title: 'Title'}) +growl('Set priority', { priority: 2 }) +growl('Show Safari icon', { image: 'Safari' }) +growl('Show icon', { image: 'path/to/icon.icns' }) +growl('Show image', { image: 'path/to/my.image.png' }) +growl('Show png filesystem icon', { image: 'png' }) +growl('Show pdf filesystem icon', { image: 'article.pdf' }) +growl('Show pdf filesystem icon', { image: 'article.pdf' }, function(){ + console.log('callback'); +}) diff --git a/node_modules/mocha/node_modules/jade/.npmignore b/node_modules/mocha/node_modules/jade/.npmignore new file mode 100644 index 0000000..5eb48f0 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/.npmignore @@ -0,0 +1,4 @@ +.DS_Store +lib-cov +testing +node_modules diff --git a/node_modules/mocha/node_modules/jade/LICENSE b/node_modules/mocha/node_modules/jade/LICENSE new file mode 100644 index 0000000..8ad0e0d --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/bin/jade b/node_modules/mocha/node_modules/jade/bin/jade new file mode 100755 index 0000000..fd4122c --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/index.js b/node_modules/mocha/node_modules/jade/index.js new file mode 100644 index 0000000..857e431 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/jade.js b/node_modules/mocha/node_modules/jade/jade.js new file mode 100644 index 0000000..22072f1 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/jade.js @@ -0,0 +1,3140 @@ +(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) { + try { + md = require('marked'); + } catch (err) { + throw new + Error('Cannot find markdown library, install markdown, discount, or marked.'); + } + } + } + } + + 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.20.1'; + +/** + * 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.textOnly) continue; + 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/mocha/node_modules/jade/jade.min.js b/node_modules/mocha/node_modules/jade/jade.min.js new file mode 100644 index 0000000..ace59ff --- /dev/null +++ b/node_modules/mocha/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){try{md=require("marked")}catch(err){throw new Error("Cannot find markdown library, install markdown, discount, or marked.")}}}}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.20.1",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/mocha/node_modules/jade/lib/compiler.js b/node_modules/mocha/node_modules/jade/lib/compiler.js new file mode 100644 index 0000000..5459da8 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/lib/compiler.js @@ -0,0 +1,503 @@ + +/*! + * 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(''); + filter.attrs = filter.attrs || {}; + filter.attrs.filename = this.options.filename; + 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/mocha/node_modules/jade/lib/doctypes.js b/node_modules/mocha/node_modules/jade/lib/doctypes.js new file mode 100644 index 0000000..feeb560 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/filters.js b/node_modules/mocha/node_modules/jade/lib/filters.js new file mode 100644 index 0000000..a7e1bef --- /dev/null +++ b/node_modules/mocha/node_modules/jade/lib/filters.js @@ -0,0 +1,97 @@ + +/*! + * 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) { + try { + md = require('marked'); + } catch (err) { + throw new + Error('Cannot find markdown library, install markdown, discount, or marked.'); + } + } + } + } + + 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/mocha/node_modules/jade/lib/inline-tags.js b/node_modules/mocha/node_modules/jade/lib/inline-tags.js new file mode 100644 index 0000000..491de0b --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/jade.js b/node_modules/mocha/node_modules/jade/lib/jade.js new file mode 100644 index 0000000..9312cf7 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/lib/jade.js @@ -0,0 +1,237 @@ +/*! + * 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.3'; + +/** + * 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/mocha/node_modules/jade/lib/lexer.js b/node_modules/mocha/node_modules/jade/lib/lexer.js new file mode 100644 index 0000000..aac94f5 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/block-comment.js b/node_modules/mocha/node_modules/jade/lib/nodes/block-comment.js new file mode 100644 index 0000000..4f41e4a --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/block.js b/node_modules/mocha/node_modules/jade/lib/nodes/block.js new file mode 100644 index 0000000..db63c77 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/lib/nodes/block.js @@ -0,0 +1,100 @@ + +/*! + * 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.textOnly) continue; + 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/mocha/node_modules/jade/lib/nodes/case.js b/node_modules/mocha/node_modules/jade/lib/nodes/case.js new file mode 100644 index 0000000..08ff033 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/code.js b/node_modules/mocha/node_modules/jade/lib/nodes/code.js new file mode 100644 index 0000000..babc675 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/comment.js b/node_modules/mocha/node_modules/jade/lib/nodes/comment.js new file mode 100644 index 0000000..2e1469e --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/doctype.js b/node_modules/mocha/node_modules/jade/lib/nodes/doctype.js new file mode 100644 index 0000000..b8f33e5 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/each.js b/node_modules/mocha/node_modules/jade/lib/nodes/each.js new file mode 100644 index 0000000..f54101f --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/filter.js b/node_modules/mocha/node_modules/jade/lib/nodes/filter.js new file mode 100644 index 0000000..5a0a237 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/index.js b/node_modules/mocha/node_modules/jade/lib/nodes/index.js new file mode 100644 index 0000000..386ad2f --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/literal.js b/node_modules/mocha/node_modules/jade/lib/nodes/literal.js new file mode 100644 index 0000000..3ddab65 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/mixin.js b/node_modules/mocha/node_modules/jade/lib/nodes/mixin.js new file mode 100644 index 0000000..f007c84 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/node.js b/node_modules/mocha/node_modules/jade/lib/nodes/node.js new file mode 100644 index 0000000..0669e67 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/tag.js b/node_modules/mocha/node_modules/jade/lib/nodes/tag.js new file mode 100644 index 0000000..35993c9 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/nodes/text.js b/node_modules/mocha/node_modules/jade/lib/nodes/text.js new file mode 100644 index 0000000..3baff4b --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/parser.js b/node_modules/mocha/node_modules/jade/lib/parser.js new file mode 100644 index 0000000..3dcfb57 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/runtime.js b/node_modules/mocha/node_modules/jade/lib/runtime.js new file mode 100644 index 0000000..7b357ca --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/self-closing.js b/node_modules/mocha/node_modules/jade/lib/self-closing.js new file mode 100644 index 0000000..293e7f8 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/lib/utils.js b/node_modules/mocha/node_modules/jade/lib/utils.js new file mode 100644 index 0000000..ff46d02 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/.gitignore.orig b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/.gitignore.orig new file mode 100644 index 0000000..9303c34 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/.gitignore.rej b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/.gitignore.rej new file mode 100644 index 0000000..69244ff --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/.npmignore b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/.npmignore new file mode 100644 index 0000000..9303c34 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/.npmignore @@ -0,0 +1,2 @@ +node_modules/ +npm-debug.log \ No newline at end of file diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/.travis.yml b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/.travis.yml new file mode 100644 index 0000000..f1d0f13 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.4 + - 0.6 diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/LICENSE b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/LICENSE new file mode 100644 index 0000000..432d1ae --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/README.markdown b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/README.markdown new file mode 100644 index 0000000..40de04f --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/README.markdown @@ -0,0 +1,61 @@ +mkdirp +====== + +Like `mkdir -p`, but in node.js! + +[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp) + +example +======= + +pow.js +------ + var mkdirp = require('mkdirp'); + + mkdirp('/tmp/foo/bar/baz', 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`. + +If `mode` isn't specified, it defaults to `0777 & (~process.umask())`. + +`cb(err, made)` fires with the error or the first directory `made` +that had to be created, if any. + +mkdirp.sync(dir, mode) +---------------------- + +Synchronously create a new directory and any necessary subdirectories at `dir` +with octal permission string `mode`. + +If `mode` isn't specified, it defaults to `0777 & (~process.umask())`. + +Returns the first directory that had to be created, if any. + +install +======= + +With [npm](http://npmjs.org) do: + + npm install mkdirp + +license +======= + +MIT/X11 diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js new file mode 100644 index 0000000..e692421 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js @@ -0,0 +1,6 @@ +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', function (err) { + if (err) console.error(err) + else console.log('pow!') +}); diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js.orig b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js.orig new file mode 100644 index 0000000..7741462 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js.rej b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/examples/pow.js.rej new file mode 100644 index 0000000..81e7f43 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/index.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/index.js new file mode 100644 index 0000000..6d81c62 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/index.js @@ -0,0 +1,83 @@ +var path = require('path'); +var fs = require('fs'); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, mode, f, made) { + if (typeof mode === 'function' || mode === undefined) { + f = mode; + mode = 0777 & (~process.umask()); + } + if (!made) made = null; + + var cb = f || function () {}; + if (typeof mode === 'string') mode = parseInt(mode, 8); + p = path.resolve(p); + + fs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + mkdirP(path.dirname(p), mode, function (er, made) { + if (er) cb(er, made); + else mkdirP(p, mode, cb, made); + }); + 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, made) + else cb(null, made); + }); + break; + + default: + cb(er, made); + break; + } + }); +} + +mkdirP.sync = function sync (p, mode, made) { + if (mode === undefined) { + mode = 0777 & (~process.umask()); + } + if (!made) made = null; + + if (typeof mode === 'string') mode = parseInt(mode, 8); + p = path.resolve(p); + + try { + fs.mkdirSync(p, mode); + made = made || p; + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + made = sync(path.dirname(p), mode, made); + sync(p, mode, made); + break; + + case 'EEXIST' : + var stat; + try { + stat = fs.statSync(p); + } + catch (err1) { + throw err0; + } + if (!stat.isDirectory()) throw err0; + break; + default : + throw err0 + break; + } + } + + return made; +}; diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/package.json b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/package.json new file mode 100644 index 0000000..3e7363f --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/package.json @@ -0,0 +1,37 @@ +{ + "name": "mkdirp", + "description": "Recursively mkdir, like `mkdir -p`", + "version": "0.3.1", + "author": { + "name": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net" + }, + "main": "./index", + "keywords": [ + "mkdir", + "directory" + ], + "repository": { + "type": "git", + "url": "git://github.com/substack/node-mkdirp.git" + }, + "scripts": { + "test": "tap test/*.js" + }, + "devDependencies": { + "tap": "~0.2.4" + }, + "license": "MIT/X11", + "engines": { + "node": "*" + }, + "_id": "mkdirp@0.3.1", + "dependencies": {}, + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "mkdirp@>= 0.0.7" +} diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/chmod.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/chmod.js new file mode 100644 index 0000000..520dcb8 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/chmod.js @@ -0,0 +1,38 @@ +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.end(); + }); + }); +}); diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/clobber.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/clobber.js new file mode 100644 index 0000000..0eb7099 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/test/mkdirp.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/mkdirp.js new file mode 100644 index 0000000..b07cd70 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/test/perm.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/perm.js new file mode 100644 index 0000000..23a7abb --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/test/perm_sync.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/perm_sync.js new file mode 100644 index 0000000..f685f60 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/test/race.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/race.js new file mode 100644 index 0000000..96a0447 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/test/rel.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/rel.js new file mode 100644 index 0000000..7985824 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/node_modules/mkdirp/test/return.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/return.js new file mode 100644 index 0000000..bce68e5 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/return.js @@ -0,0 +1,25 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('return value', function (t) { + t.plan(4); + 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('/'); + + // should return the first dir created. + // By this point, it would be profoundly surprising if /tmp didn't + // already exist, since every other test makes things in there. + mkdirp(file, function (err, made) { + t.ifError(err); + t.equal(made, '/tmp/' + x); + mkdirp(file, function (err, made) { + t.ifError(err); + t.equal(made, null); + }); + }); +}); diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/return_sync.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/return_sync.js new file mode 100644 index 0000000..7c222d3 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/return_sync.js @@ -0,0 +1,24 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('return value', 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('/'); + + // should return the first dir created. + // By this point, it would be profoundly surprising if /tmp didn't + // already exist, since every other test makes things in there. + // Note that this will throw on failure, which will fail the test. + var made = mkdirp.sync(file); + t.equal(made, '/tmp/' + x); + + // making the same file again should have no effect. + made = mkdirp.sync(file); + t.equal(made, null); +}); diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/sync.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/sync.js new file mode 100644 index 0000000..7530cad --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/sync.js @@ -0,0 +1,32 @@ +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('/'); + + try { + mkdirp.sync(file, 0755); + } catch (err) { + t.fail(err); + return t.end(); + } + + 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/mocha/node_modules/jade/node_modules/mkdirp/test/umask.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/umask.js new file mode 100644 index 0000000..64ccafe --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/umask.js @@ -0,0 +1,28 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('implicit mode from umask', 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, 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, 0777 & (~process.umask())); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/umask_sync.js b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/umask_sync.js new file mode 100644 index 0000000..4bd5376 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/node_modules/mkdirp/test/umask_sync.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('umask sync modes', 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('/'); + + try { + mkdirp.sync(file, 0755); + } catch (err) { + t.fail(err); + return t.end(); + } + + 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, (0777 & (~process.umask()))); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }); + }); +}); diff --git a/node_modules/mocha/node_modules/jade/package.json b/node_modules/mocha/node_modules/jade/package.json new file mode 100644 index 0000000..e77e908 --- /dev/null +++ b/node_modules/mocha/node_modules/jade/package.json @@ -0,0 +1,42 @@ +{ + "name": "jade", + "description": "Jade template engine", + "version": "0.20.3", + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/jade.git" + }, + "main": "./index.js", + "bin": { + "jade": "./bin/jade" + }, + "dependencies": { + "commander": "0.5.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" + }, + "_id": "jade@0.20.3", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "jade@0.20.3" +} diff --git a/node_modules/mocha/node_modules/jade/runtime.js b/node_modules/mocha/node_modules/jade/runtime.js new file mode 100644 index 0000000..39f8a28 --- /dev/null +++ b/node_modules/mocha/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/mocha/node_modules/jade/runtime.min.js b/node_modules/mocha/node_modules/jade/runtime.min.js new file mode 100644 index 0000000..8c19a98 --- /dev/null +++ b/node_modules/mocha/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/mocha/package.json b/node_modules/mocha/package.json new file mode 100644 index 0000000..f77984b --- /dev/null +++ b/node_modules/mocha/package.json @@ -0,0 +1,48 @@ +{ + "name": "mocha", + "version": "1.0.1", + "description": "simple, flexible, fun test framework", + "keywords": [ + "test", + "bdd", + "tdd", + "tap" + ], + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "repository": { + "type": "git", + "url": "git://github.com/visionmedia/mocha.git" + }, + "main": "./index", + "bin": { + "mocha": "./bin/mocha", + "_mocha": "./bin/_mocha" + }, + "engines": { + "node": ">= 0.4.x < 0.8.0" + }, + "scripts": { + "test": "make test" + }, + "dependencies": { + "commander": "0.5.x", + "growl": "1.5.x", + "jade": "0.20.3", + "diff": "1.0.2", + "debug": "*" + }, + "devDependencies": { + "should": "*", + "coffee-script": "1.2" + }, + "_id": "mocha@1.0.1", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "_from": "mocha@>= 0.14.1" +} diff --git a/node_modules/mongodb/.travis.yml b/node_modules/mongodb/.travis.yml new file mode 100644 index 0000000..90b208a --- /dev/null +++ b/node_modules/mongodb/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - 0.4 + - 0.6 + - 0.7 # development version of 0.8, may be unstable \ No newline at end of file diff --git a/node_modules/mongodb/Makefile b/node_modules/mongodb/Makefile new file mode 100644 index 0000000..de11cbe --- /dev/null +++ b/node_modules/mongodb/Makefile @@ -0,0 +1,71 @@ +NODE = node +NPM = npm +NODEUNIT = node_modules/nodeunit/bin/nodeunit +DOX = node_modules/dox/bin/dox +name = all + +total: build_native + +build_native: + # $(MAKE) -C ./external-libs/bson all + +build_native_debug: + $(MAKE) -C ./external-libs/bson all_debug + +build_native_clang: + $(MAKE) -C ./external-libs/bson clang + +build_native_clang_debug: + $(MAKE) -C ./external-libs/bson clang_debug + +clean_native: + $(MAKE) -C ./external-libs/bson clean + +test: build_native + @echo "\n == Run All tests minus replicaset tests==" + $(NODE) dev/tools/test_all.js --noreplicaset --boot + +test_pure: build_native + @echo "\n == Run All tests minus replicaset tests==" + $(NODE) dev/tools/test_all.js --noreplicaset --boot --noactive + +test_junit: build_native + @echo "\n == Run All tests minus replicaset tests==" + $(NODE) dev/tools/test_all.js --junit --noreplicaset + +test_nodeunit_pure: + @echo "\n == Execute Test Suite using Pure JS BSON Parser == " + @$(NODEUNIT) test/ test/gridstore test/bson + +test_js: + @$(NODEUNIT) $(TESTS) + +test_nodeunit_replicaset_pure: + @echo "\n == Execute Test Suite using Pure JS BSON Parser == " + @$(NODEUNIT) test/replicaset + +test_nodeunit_native: + @echo "\n == Execute Test Suite using Native BSON Parser == " + @TEST_NATIVE=TRUE $(NODEUNIT) test/ test/gridstore test/bson + +test_nodeunit_replicaset_native: + @echo "\n == Execute Test Suite using Native BSON Parser == " + @TEST_NATIVE=TRUE $(NODEUNIT) test/replicaset + +test_all: build_native + @echo "\n == Run All tests ==" + $(NODE) dev/tools/test_all.js --boot + +test_all_junit: build_native + @echo "\n == Run All tests ==" + $(NODE) dev/tools/test_all.js --junit --boot + +clean: + rm ./external-libs/bson/bson.node + rm -r ./external-libs/bson/build + +generate_docs: + $(NODE) dev/tools/build-docs.js + make --directory=./docs/sphinx-docs --file=Makefile html + +.PHONY: total diff --git a/node_modules/mongodb/external-libs/bson/Makefile b/node_modules/mongodb/external-libs/bson/Makefile new file mode 100644 index 0000000..ad877d4 --- /dev/null +++ b/node_modules/mongodb/external-libs/bson/Makefile @@ -0,0 +1,45 @@ +NODE = node +name = all +JOBS = 1 + +all: + rm -rf build .lock-wscript bson.node + node-waf configure build + cp -R ./build/Release/bson.node . || true + @$(NODE) --expose-gc test/test_bson.js + @$(NODE) --expose-gc test/test_full_bson.js + # @$(NODE) --expose-gc test/test_stackless_bson.js + +all_debug: + rm -rf build .lock-wscript bson.node + node-waf --debug configure build + cp -R ./build/Release/bson.node . || true + @$(NODE) --expose-gc test/test_bson.js + @$(NODE) --expose-gc test/test_full_bson.js + # @$(NODE) --expose-gc test/test_stackless_bson.js + +test: + @$(NODE) --expose-gc test/test_bson.js + @$(NODE) --expose-gc test/test_full_bson.js + # @$(NODE) --expose-gc test/test_stackless_bson.js + +clang: + rm -rf build .lock-wscript bson.node + CXX=clang node-waf configure build + cp -R ./build/Release/bson.node . || true + @$(NODE) --expose-gc test/test_bson.js + @$(NODE) --expose-gc test/test_full_bson.js + # @$(NODE) --expose-gc test/test_stackless_bson.js + +clang_debug: + rm -rf build .lock-wscript bson.node + CXX=clang node-waf --debug configure build + cp -R ./build/Release/bson.node . || true + @$(NODE) --expose-gc test/test_bson.js + @$(NODE) --expose-gc test/test_full_bson.js + # @$(NODE) --expose-gc test/test_stackless_bson.js + +clean: + rm -rf build .lock-wscript bson.node + +.PHONY: all \ No newline at end of file diff --git a/node_modules/mongodb/external-libs/bson/bson.cc b/node_modules/mongodb/external-libs/bson/bson.cc new file mode 100644 index 0000000..8906eea --- /dev/null +++ b/node_modules/mongodb/external-libs/bson/bson.cc @@ -0,0 +1,2165 @@ +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bson.h" + +using namespace v8; +using namespace node; +using namespace std; + +// BSON DATA TYPES +const uint32_t BSON_DATA_NUMBER = 1; +const uint32_t BSON_DATA_STRING = 2; +const uint32_t BSON_DATA_OBJECT = 3; +const uint32_t BSON_DATA_ARRAY = 4; +const uint32_t BSON_DATA_BINARY = 5; +const uint32_t BSON_DATA_OID = 7; +const uint32_t BSON_DATA_BOOLEAN = 8; +const uint32_t BSON_DATA_DATE = 9; +const uint32_t BSON_DATA_NULL = 10; +const uint32_t BSON_DATA_REGEXP = 11; +const uint32_t BSON_DATA_CODE = 13; +const uint32_t BSON_DATA_SYMBOL = 14; +const uint32_t BSON_DATA_CODE_W_SCOPE = 15; +const uint32_t BSON_DATA_INT = 16; +const uint32_t BSON_DATA_TIMESTAMP = 17; +const uint32_t BSON_DATA_LONG = 18; +const uint32_t BSON_DATA_MIN_KEY = 0xff; +const uint32_t BSON_DATA_MAX_KEY = 0x7f; + +const int32_t BSON_INT32_MAX = (int32_t)2147483647L; +const int32_t BSON_INT32_MIN = (int32_t)(-1) * 2147483648L; + +const int64_t BSON_INT64_MAX = ((int64_t)1 << 63) - 1; +const int64_t BSON_INT64_MIN = (int64_t)-1 << 63; + +const int64_t JS_INT_MAX = (int64_t)1 << 53; +const int64_t JS_INT_MIN = (int64_t)-1 << 53; + +static Handle VException(const char *msg) { + HandleScope scope; + return ThrowException(Exception::Error(String::New(msg))); + }; + +Persistent BSON::constructor_template; + +void BSON::Initialize(v8::Handle target) { + // Grab the scope of the call from Node + HandleScope scope; + // Define a new function template + Local t = FunctionTemplate::New(New); + constructor_template = Persistent::New(t); + constructor_template->InstanceTemplate()->SetInternalFieldCount(1); + constructor_template->SetClassName(String::NewSymbol("BSON")); + + // Instance methods + NODE_SET_PROTOTYPE_METHOD(constructor_template, "calculateObjectSize", CalculateObjectSize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "serialize", BSONSerialize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "serializeWithBufferAndIndex", SerializeWithBufferAndIndex); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserialize", BSONDeserialize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserializeStream", BSONDeserializeStream); + + // Experimental + // NODE_SET_PROTOTYPE_METHOD(constructor_template, "calculateObjectSize2", CalculateObjectSize2); + // NODE_SET_PROTOTYPE_METHOD(constructor_template, "serialize2", BSONSerialize2); + // NODE_SET_METHOD(constructor_template->GetFunction(), "serialize2", BSONSerialize2); + + target->ForceSet(String::NewSymbol("BSON"), constructor_template->GetFunction()); +} + +// Create a new instance of BSON and assing it the existing context +Handle BSON::New(const Arguments &args) { + HandleScope scope; + + // Check that we have an array + if(args.Length() == 1 && args[0]->IsArray()) { + // Cast the array to a local reference + Local array = Local::Cast(args[0]); + + if(array->Length() > 0) { + // Create a bson object instance and return it + BSON *bson = new BSON(); + + // Setup pre-allocated comparision objects + bson->_bsontypeString = Persistent::New(String::New("_bsontype")); + bson->_longLowString = Persistent::New(String::New("low_")); + bson->_longHighString = Persistent::New(String::New("high_")); + bson->_objectIDidString = Persistent::New(String::New("id")); + bson->_binaryPositionString = Persistent::New(String::New("position")); + bson->_binarySubTypeString = Persistent::New(String::New("sub_type")); + bson->_binaryBufferString = Persistent::New(String::New("buffer")); + bson->_doubleValueString = Persistent::New(String::New("value")); + bson->_symbolValueString = Persistent::New(String::New("value")); + bson->_dbRefRefString = Persistent::New(String::New("$ref")); + bson->_dbRefIdRefString = Persistent::New(String::New("$id")); + bson->_dbRefDbRefString = Persistent::New(String::New("$db")); + bson->_dbRefNamespaceString = Persistent::New(String::New("namespace")); + bson->_dbRefDbString = Persistent::New(String::New("db")); + bson->_dbRefOidString = Persistent::New(String::New("oid")); + + // total number of found classes + uint32_t numberOfClasses = 0; + + // Iterate over all entries to save the instantiate funtions + for(uint32_t i = 0; i < array->Length(); i++) { + // Let's get a reference to the function + Local func = Local::Cast(array->Get(i)); + Local functionName = func->GetName()->ToString(); + + // Save the functions making them persistant handles (they don't get collected) + if(functionName->StrictEquals(String::New("Long"))) { + bson->longConstructor = Persistent::New(func); + bson->longString = Persistent::New(String::New("Long")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("ObjectID"))) { + bson->objectIDConstructor = Persistent::New(func); + bson->objectIDString = Persistent::New(String::New("ObjectID")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Binary"))) { + bson->binaryConstructor = Persistent::New(func); + bson->binaryString = Persistent::New(String::New("Binary")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Code"))) { + bson->codeConstructor = Persistent::New(func); + bson->codeString = Persistent::New(String::New("Code")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("DBRef"))) { + bson->dbrefConstructor = Persistent::New(func); + bson->dbrefString = Persistent::New(String::New("DBRef")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Symbol"))) { + bson->symbolConstructor = Persistent::New(func); + bson->symbolString = Persistent::New(String::New("Symbol")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Double"))) { + bson->doubleConstructor = Persistent::New(func); + bson->doubleString = Persistent::New(String::New("Double")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Timestamp"))) { + bson->timestampConstructor = Persistent::New(func); + bson->timestampString = Persistent::New(String::New("Timestamp")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("MinKey"))) { + bson->minKeyConstructor = Persistent::New(func); + bson->minKeyString = Persistent::New(String::New("MinKey")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("MaxKey"))) { + bson->maxKeyConstructor = Persistent::New(func); + bson->maxKeyString = Persistent::New(String::New("MaxKey")); + numberOfClasses = numberOfClasses + 1; + } + } + + // Check if we have the right number of constructors otherwise throw an error + if(numberOfClasses != 10) { + // Destroy object + delete(bson); + // Fire exception + return VException("Missing function constructor for either [Long/ObjectID/Binary/Code/DbRef/Symbol/Double/Timestamp/MinKey/MaxKey]"); + } else { + bson->Wrap(args.This()); + return args.This(); + } + } else { + return VException("No types passed in"); + } + } else { + return VException("Argument passed in must be an array of types"); + } +} + +void BSON::write_int32(char *data, uint32_t value) { + // Write the int to the char* + memcpy(data, &value, 4); +} + +void BSON::write_double(char *data, double value) { + // Write the double to the char* + memcpy(data, &value, 8); +} + +void BSON::write_int64(char *data, int64_t value) { + // Write the int to the char* + memcpy(data, &value, 8); +} + +char *BSON::check_key(Local key) { + // Allocate space for they key string + char *key_str = (char *)malloc(key->Utf8Length() * sizeof(char) + 1); + // Error string + char *error_str = (char *)malloc(256 * sizeof(char)); + // Decode the key + ssize_t len = DecodeBytes(key, BINARY); + DecodeWrite(key_str, len, key, BINARY); + *(key_str + key->Utf8Length()) = '\0'; + // Check if we have a valid key + if(key->Utf8Length() > 0 && *(key_str) == '$') { + // Create the string + sprintf(error_str, "key %s must not start with '$'", key_str); + // Free up memory + free(key_str); + // Throw exception with string + throw error_str; + } else if(key->Utf8Length() > 0 && strchr(key_str, '.') != NULL) { + // Create the string + sprintf(error_str, "key %s must not contain '.'", key_str); + // Free up memory + free(key_str); + // Throw exception with string + throw error_str; + } + // Free allocated space + free(key_str); + free(error_str); + // Return No check key error + return NULL; +} + +const char* BSON::ToCString(const v8::String::Utf8Value& value) { + return *value ? *value : ""; +} + +Handle BSON::decodeDBref(BSON *bson, Local ref, Local oid, Local db) { + HandleScope scope; + Local argv[] = {ref, oid, db}; + Handle dbrefObj = bson->dbrefConstructor->NewInstance(3, argv); + return scope.Close(dbrefObj); +} + +Handle BSON::decodeCode(BSON *bson, char *code, Handle scope_object) { + HandleScope scope; + + Local argv[] = {String::New(code), scope_object->ToObject()}; + Handle codeObj = bson->codeConstructor->NewInstance(2, argv); + return scope.Close(codeObj); +} + +Handle BSON::decodeBinary(BSON *bson, uint32_t sub_type, uint32_t number_of_bytes, char *data) { + HandleScope scope; + + // Create a buffer object that wraps the raw stream + Buffer *bufferObj = Buffer::New(data, number_of_bytes); + // Arguments to be passed to create the binary + Handle argv[] = {bufferObj->handle_, Uint32::New(sub_type)}; + // Return the buffer handle + Local bufferObjHandle = bson->binaryConstructor->NewInstance(2, argv); + // Close the scope + return scope.Close(bufferObjHandle); +} + +Handle BSON::decodeOid(BSON *bson, char *oid) { + HandleScope scope; + + // Encode the string (string - null termiating character) + Local bin_value = Encode(oid, 12, BINARY)->ToString(); + + // Return the id object + Local argv[] = {bin_value}; + Local oidObj = bson->objectIDConstructor->NewInstance(1, argv); + return scope.Close(oidObj); +} + +Handle BSON::decodeLong(BSON *bson, char *data, uint32_t index) { + HandleScope scope; + + // Decode the integer value + int32_t lowBits = 0; + int32_t highBits = 0; + memcpy(&lowBits, (data + index), 4); + memcpy(&highBits, (data + index + 4), 4); + + // Decode 64bit value + int64_t value = 0; + memcpy(&value, (data + index), 8); + + // If value is < 2^53 and >-2^53 + if((highBits < 0x200000 || (highBits == 0x200000 && lowBits == 0)) && highBits >= -0x200000) { + int64_t finalValue = 0; + memcpy(&finalValue, (data + index), 8); + return scope.Close(Number::New(finalValue)); + } + + // Instantiate the js object and pass it back + Local argv[] = {Int32::New(lowBits), Int32::New(highBits)}; + Local longObject = bson->longConstructor->NewInstance(2, argv); + return scope.Close(longObject); +} + +Handle BSON::decodeTimestamp(BSON *bson, char *data, uint32_t index) { + HandleScope scope; + + // Decode the integer value + int32_t lowBits = 0; + int32_t highBits = 0; + memcpy(&lowBits, (data + index), 4); + memcpy(&highBits, (data + index + 4), 4); + + // Build timestamp + Local argv[] = {Int32::New(lowBits), Int32::New(highBits)}; + Handle timestamp_obj = bson->timestampConstructor->NewInstance(2, argv); + return scope.Close(timestamp_obj); +} + +// Search for 0 terminated C string and return the string +char* BSON::extract_string(char *data, uint32_t offset) { + char *prt = strchr((data + offset), '\0'); + if(prt == NULL) return NULL; + // Figure out the length of the string + uint32_t length = (prt - data) - offset; + // Allocate memory for the new string + char *string_name = (char *)malloc((length * sizeof(char)) + 1); + // Copy the variable into the string_name + strncpy(string_name, (data + offset), length); + // Ensure the string is null terminated + *(string_name + length) = '\0'; + // Return the unpacked string + return string_name; +} + +// Decode a byte +uint16_t BSON::deserialize_int8(char *data, uint32_t offset) { + uint16_t value = 0; + value |= *(data + offset + 0); + return value; +} + +// Requires a 4 byte char array +uint32_t BSON::deserialize_int32(char* data, uint32_t offset) { + uint32_t value = 0; + memcpy(&value, (data + offset), 4); + return value; +} + +//------------------------------------------------------------------------------------------------ +// +// Experimental +// +//------------------------------------------------------------------------------------------------ +Handle BSON::CalculateObjectSize2(const Arguments &args) { + HandleScope scope; + // Ensure we have a valid object + if(args.Length() == 1 && !args[0]->IsObject()) return VException("One argument required - [object]"); + if(args.Length() > 1) return VException("One argument required - [object]"); + // Calculate size of the object + uint32_t object_size = BSON::calculate_object_size2(args[0]); + // Return the object size + return scope.Close(Uint32::New(object_size)); +} + +uint32_t BSON::calculate_object_size2(Handle value) { + // Final object size + uint32_t object_size = (4 + 1); + uint32_t stackIndex = 0; + // Controls the flow + bool done = false; + bool finished = false; + + // Current object we are processing + Local currentObject = value->ToObject(); + + // Current list of object keys + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + Local keys = currentObject->GetPropertyNames(); + #else + Local keys = currentObject->GetOwnPropertyNames(); + #endif + + // Contains pointer to keysIndex + uint32_t keysIndex = 0; + uint32_t keysLength = keys->Length(); + + // printf("=================================================================================\n"); + // printf("Start serializing\n"); + + while(!done) { + // If the index is bigger than the number of keys for the object + // we finished up the previous object and are ready for the next one + if(keysIndex >= keysLength) { + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + keys = currentObject->GetPropertyNames(); + #else + keys = currentObject->GetOwnPropertyNames(); + #endif + keysLength = keys->Length(); + } + + // Iterate over all the keys + while(keysIndex < keysLength) { + // Fetch the key name + Local name = keys->Get(keysIndex++)->ToString(); + // Fetch the object related to the key + Local value = currentObject->Get(name); + // Add size of the name, plus zero, plus type + object_size += name->Utf8Length() + 1 + 1; + + // If we have a string + if(value->IsString()) { + object_size += value->ToString()->Utf8Length() + 1 + 4; + } else if(value->IsNumber()) { + // Check if we have a float value or a long value + Local number = value->ToNumber(); + double d_number = number->NumberValue(); + int64_t l_number = number->IntegerValue(); + // Check if we have a double value and not a int64 + double d_result = d_number - l_number; + // If we have a value after subtracting the integer value we have a float + if(d_result > 0 || d_result < 0) { + object_size = object_size + 8; + } else if(l_number <= BSON_INT32_MAX && l_number >= BSON_INT32_MIN) { + object_size = object_size + 4; + } else { + object_size = object_size + 8; + } + } else if(value->IsBoolean()) { + object_size = object_size + 1; + } else if(value->IsDate()) { + object_size = object_size + 8; + } else if(value->IsRegExp()) { + // Fetch the string for the regexp + Handle regExp = Handle::Cast(value); + ssize_t len = DecodeBytes(regExp->GetSource(), UTF8); + int flags = regExp->GetFlags(); + + // global + if((flags & (1 << 0)) != 0) len++; + // ignorecase + if((flags & (1 << 1)) != 0) len++; + //multiline + if((flags & (1 << 2)) != 0) len++; + // if((flags & (1 << 2)) != 0) len++; + // Calculate the space needed for the regexp: size of string - 2 for the /'ses +2 for null termiations + object_size = object_size + len + 2; + } else if(value->IsNull() || value->IsUndefined()) { + } + // } else if(value->IsNumber()) { + // // Check if we have a float value or a long value + // Local number = value->ToNumber(); + // double d_number = number->NumberValue(); + // int64_t l_number = number->IntegerValue(); + // // Check if we have a double value and not a int64 + // double d_result = d_number - l_number; + // // If we have a value after subtracting the integer value we have a float + // if(d_result > 0 || d_result < 0) { + // object_size = name->Utf8Length() + 1 + object_size + 8 + 1; + // } else if(l_number <= BSON_INT32_MAX && l_number >= BSON_INT32_MIN) { + // object_size = name->Utf8Length() + 1 + object_size + 4 + 1; + // } else { + // object_size = name->Utf8Length() + 1 + object_size + 8 + 1; + // } + // } else if(value->IsObject()) { + // printf("------------- hello\n"); + // } + } + + // If we have finished all the keys + if(keysIndex == keysLength) { + finished = false; + } + + // Validate the stack + if(stackIndex == 0) { + // printf("======================================================================== 3\n"); + done = true; + } else if(finished || keysIndex == keysLength) { + // Pop off the stack + stackIndex = stackIndex - 1; + // Fetch the current object stack + // vector > currentObjectStored = stack.back(); + // stack.pop_back(); + // // Unroll the current object + // currentObject = currentObjectStored.back()->ToObject(); + // currentObjectStored.pop_back(); + // // Unroll the keysIndex + // keys = Local::Cast(currentObjectStored.back()->ToObject()); + // currentObjectStored.pop_back(); + // // Unroll the keysIndex + // keysIndex = currentObjectStored.back()->ToUint32()->Value(); + // currentObjectStored.pop_back(); + // // Check if we finished up + // if(keysIndex == keys->Length()) { + // finished = true; + // } + } + } + + return object_size; +} + +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +Handle BSON::BSONDeserialize(const Arguments &args) { + HandleScope scope; + + // Ensure that we have an parameter + if(Buffer::HasInstance(args[0]) && args.Length() > 1) return VException("One argument required - buffer1."); + if(args[0]->IsString() && args.Length() > 1) return VException("One argument required - string1."); + // Throw an exception if the argument is not of type Buffer + if(!Buffer::HasInstance(args[0]) && !args[0]->IsString()) return VException("Argument must be a Buffer or String."); + + // Define pointer to data + char *data; + Local obj = args[0]->ToObject(); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // If we passed in a buffer, let's unpack it, otherwise let's unpack the string + if(Buffer::HasInstance(obj)) { + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(obj); + data = buffer->data(); + uint32_t length = buffer->length(); + #else + data = Buffer::Data(obj); + uint32_t length = Buffer::Length(obj); + #endif + + // Validate that we have at least 5 bytes + if(length < 5) { + return VException("corrupt bson message < 5 bytes long"); + } + + // Deserialize the data + return BSON::deserialize(bson, data, length, 0, NULL); + } else { + // The length of the data for this encoding + ssize_t len = DecodeBytes(args[0], BINARY); + + // Validate that we have at least 5 bytes + if(len < 5) { + return VException("corrupt bson message < 5 bytes long"); + } + + // Let's define the buffer size + data = (char *)malloc(len); + // Write the data to the buffer from the string object + ssize_t written = DecodeWrite(data, len, args[0], BINARY); + // Assert that we wrote the same number of bytes as we have length + assert(written == len); + // Get result + Handle result = BSON::deserialize(bson, data, len, 0, NULL); + // Free memory + free(data); + // Deserialize the content + return result; + } +} + +// Deserialize the stream +Handle BSON::deserialize(BSON *bson, char *data, uint32_t inDataLength, uint32_t startIndex, bool is_array_item) { + HandleScope scope; + // Holds references to the objects that are going to be returned + Local return_data = Object::New(); + Local return_array = Array::New(); + // The current index in the char data + uint32_t index = startIndex; + // Decode the size of the BSON data structure + uint32_t size = BSON::deserialize_int32(data, index); + + // If we have an illegal message size + if(size > inDataLength) return VException("corrupt bson message"); + + // Data length + uint32_t dataLength = index + size; + + // Adjust the index to point to next piece + index = index + 4; + + // While we have data left let's decode + while(index < dataLength) { + // Read the first to bytes to indicate the type of object we are decoding + uint8_t type = BSON::deserialize_int8(data, index); + // Adjust index to skip type byte + index = index + 1; + + if(type == BSON_DATA_STRING) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Read the length of the string (next 4 bytes) + uint32_t string_size = BSON::deserialize_int32(data, index); + // Adjust index to point to start of string + index = index + 4; + // Decode the string and add zero terminating value at the end of the string + char *value = (char *)malloc((string_size * sizeof(char))); + strncpy(value, (data + index), string_size); + // Encode the string (string - null termiating character) + Local utf8_encoded_str = Encode(value, string_size - 1, UTF8)->ToString(); + // Add the value to the data + if(is_array_item) { + return_array->Set(Number::New(insert_index), utf8_encoded_str); + } else { + return_data->ForceSet(String::New(string_name), utf8_encoded_str); + } + + // Adjust index + index = index + string_size; + // Free up the memory + free(value); + free(string_name); + } else if(type == BSON_DATA_INT) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Decode the integer value + uint32_t value = 0; + memcpy(&value, (data + index), 4); + + // Adjust the index for the size of the value + index = index + 4; + // Add the element to the object + if(is_array_item) { + return_array->Set(Integer::New(insert_index), Integer::New(value)); + } else { + return_data->ForceSet(String::New(string_name), Integer::New(value)); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_TIMESTAMP) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), BSON::decodeTimestamp(bson, data, index)); + } else { + return_data->ForceSet(String::New(string_name), BSON::decodeTimestamp(bson, data, index)); + } + + // Adjust the index for the size of the value + index = index + 8; + + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_LONG) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), BSON::decodeLong(bson, data, index)); + } else { + return_data->ForceSet(String::New(string_name), BSON::decodeLong(bson, data, index)); + } + + // Adjust the index for the size of the value + index = index + 8; + + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_NUMBER) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Decode the integer value + double value = 0; + memcpy(&value, (data + index), 8); + // Adjust the index for the size of the value + index = index + 8; + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), Number::New(value)); + } else { + return_data->ForceSet(String::New(string_name), Number::New(value)); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_MIN_KEY) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Create new MinKey + Local minKey = bson->minKeyConstructor->NewInstance(); + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), minKey); + } else { + return_data->ForceSet(String::New(string_name), minKey); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_MAX_KEY) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Create new MinKey + Local maxKey = bson->maxKeyConstructor->NewInstance(); + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), maxKey); + } else { + return_data->ForceSet(String::New(string_name), maxKey); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_NULL) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), Null()); + } else { + return_data->ForceSet(String::New(string_name), Null()); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_BOOLEAN) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Decode the boolean value + char bool_value = *(data + index); + // Adjust the index for the size of the value + index = index + 1; + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), bool_value == 1 ? Boolean::New(true) : Boolean::New(false)); + } else { + return_data->ForceSet(String::New(string_name), bool_value == 1 ? Boolean::New(true) : Boolean::New(false)); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_DATE) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Decode the value 64 bit integer + int64_t value = 0; + memcpy(&value, (data + index), 8); + // Adjust the index for the size of the value + index = index + 8; + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), Date::New((double)value)); + } else { + return_data->ForceSet(String::New(string_name), Date::New((double)value)); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_REGEXP) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Length variable + int32_t length_regexp = 0; + char chr; + + // Locate end of the regexp expression \0 + while((chr = *(data + index + length_regexp)) != '\0') { + length_regexp = length_regexp + 1; + } + + // Contains the reg exp + char *reg_exp = (char *)malloc(length_regexp * sizeof(char) + 2); + // Copy the regexp from the data to the char * + memcpy(reg_exp, (data + index), (length_regexp + 1)); + // Adjust the index to skip the first part of the regular expression + index = index + length_regexp + 1; + + // Reset the length + int32_t options_length = 0; + // Locate the end of the options for the regexp terminated with a '\0' + while((chr = *(data + index + options_length)) != '\0') { + options_length = options_length + 1; + } + + // Contains the reg exp + char *options = (char *)malloc(options_length * sizeof(char) + 1); + // Copy the options from the data to the char * + memcpy(options, (data + index), (options_length + 1)); + // Adjust the index to skip the option part of the regular expression + index = index + options_length + 1; + // ARRRRGH Google does not expose regular expressions through the v8 api + // Have to use Script to instantiate the object (slower) + + // Generate the string for execution in the string context + int flag = 0; + + for(int i = 0; i < options_length; i++) { + // Multiline + if(*(options + i) == 'm') { + flag = flag | 4; + } else if(*(options + i) == 'i') { + flag = flag | 2; + } + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), RegExp::New(String::New(reg_exp), (v8::RegExp::Flags)flag)); + } else { + return_data->ForceSet(String::New(string_name), RegExp::New(String::New(reg_exp), (v8::RegExp::Flags)flag)); + } + + // Free memory + free(reg_exp); + free(options); + free(string_name); + } else if(type == BSON_DATA_OID) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // The id string + char *oid_string = (char *)malloc(12 * sizeof(char)); + // Copy the options from the data to the char * + memcpy(oid_string, (data + index), 12); + + // Adjust the index + index = index + 12; + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), BSON::decodeOid(bson, oid_string)); + } else { + return_data->ForceSet(String::New(string_name), BSON::decodeOid(bson, oid_string)); + } + + // Free memory + free(oid_string); + free(string_name); + } else if(type == BSON_DATA_BINARY) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Read the binary data size + uint32_t number_of_bytes = BSON::deserialize_int32(data, index); + // Adjust the index + index = index + 4; + // Decode the subtype, ensure it's positive + uint32_t sub_type = (int)*(data + index) & 0xff; + // Adjust the index + index = index + 1; + // Copy the binary data into a buffer + char *buffer = (char *)malloc(number_of_bytes * sizeof(char) + 1); + memcpy(buffer, (data + index), number_of_bytes); + *(buffer + number_of_bytes) = '\0'; + + // Adjust the index + index = index + number_of_bytes; + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), BSON::decodeBinary(bson, sub_type, number_of_bytes, buffer)); + } else { + return_data->ForceSet(String::New(string_name), BSON::decodeBinary(bson, sub_type, number_of_bytes, buffer)); + } + // Free memory + free(buffer); + free(string_name); + } else if(type == BSON_DATA_SYMBOL) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Read the length of the string (next 4 bytes) + uint32_t string_size = BSON::deserialize_int32(data, index); + // Adjust index to point to start of string + index = index + 4; + // Decode the string and add zero terminating value at the end of the string + char *value = (char *)malloc((string_size * sizeof(char))); + strncpy(value, (data + index), string_size); + // Encode the string (string - null termiating character) + Local utf8_encoded_str = Encode(value, string_size - 1, UTF8)->ToString(); + + // Wrap up the string in a Symbol Object + Local argv[] = {utf8_encoded_str}; + Handle symbolObj = bson->symbolConstructor->NewInstance(1, argv); + + // Add the value to the data + if(is_array_item) { + return_array->Set(Number::New(insert_index), symbolObj); + } else { + return_data->ForceSet(String::New(string_name), symbolObj); + } + + // Adjust index + index = index + string_size; + // Free up the memory + free(value); + free(string_name); + } else if(type == BSON_DATA_CODE) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Read the string size + uint32_t string_size = BSON::deserialize_int32(data, index); + // Adjust the index + index = index + 4; + // Read the string + char *code = (char *)malloc(string_size * sizeof(char) + 1); + // Copy string + terminating 0 + memcpy(code, (data + index), string_size); + + // Define empty scope object + Handle scope_object = Object::New(); + + // Define the try catch block + TryCatch try_catch; + // Decode the code object + Handle obj = BSON::decodeCode(bson, code, scope_object); + // If an error was thrown push it up the chain + if(try_catch.HasCaught()) { + free(string_name); + free(code); + // Rethrow exception + return try_catch.ReThrow(); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), obj); + } else { + return_data->ForceSet(String::New(string_name), obj); + } + + // Clean up memory allocation + free(code); + free(string_name); + } else if(type == BSON_DATA_CODE_W_SCOPE) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Total number of bytes after array index + uint32_t total_code_size = BSON::deserialize_int32(data, index); + // Adjust the index + index = index + 4; + // Read the string size + uint32_t string_size = BSON::deserialize_int32(data, index); + // Adjust the index + index = index + 4; + // Read the string + char *code = (char *)malloc(string_size * sizeof(char) + 1); + // Copy string + terminating 0 + memcpy(code, (data + index), string_size); + // Adjust the index + index = index + string_size; + // Get the scope object (bson object) + uint32_t bson_object_size = total_code_size - string_size - 8; + // Allocate bson object buffer and copy out the content + char *bson_buffer = (char *)malloc(bson_object_size * sizeof(char)); + memcpy(bson_buffer, (data + index), bson_object_size); + // Adjust the index + index = index + bson_object_size; + // Parse the bson object + Handle scope_object = BSON::deserialize(bson, bson_buffer, inDataLength, 0, false); + // Define the try catch block + TryCatch try_catch; + // Decode the code object + Handle obj = BSON::decodeCode(bson, code, scope_object); + // If an error was thrown push it up the chain + if(try_catch.HasCaught()) { + // Clean up memory allocation + free(string_name); + free(bson_buffer); + free(code); + // Rethrow exception + return try_catch.ReThrow(); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), obj); + } else { + return_data->ForceSet(String::New(string_name), obj); + } + + // Clean up memory allocation + free(code); + free(bson_buffer); + free(string_name); + } else if(type == BSON_DATA_OBJECT) { + // If this is the top level object we need to skip the undecoding + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Get the object size + uint32_t bson_object_size = BSON::deserialize_int32(data, index); + // Define the try catch block + TryCatch try_catch; + // Decode the code object + Handle obj = BSON::deserialize(bson, data + index, inDataLength, 0, false); + // Adjust the index + index = index + bson_object_size; + // If an error was thrown push it up the chain + if(try_catch.HasCaught()) { + // Rethrow exception + return try_catch.ReThrow(); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), obj); + } else { + return_data->ForceSet(String::New(string_name), obj); + } + + // Clean up memory allocation + free(string_name); + } else if(type == BSON_DATA_ARRAY) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Get the size + uint32_t array_size = BSON::deserialize_int32(data, index); + // Define the try catch block + TryCatch try_catch; + + // Decode the code object + Handle obj = BSON::deserialize(bson, data + index, inDataLength, 0, true); + // If an error was thrown push it up the chain + if(try_catch.HasCaught()) { + // Rethrow exception + return try_catch.ReThrow(); + } + // Adjust the index for the next value + index = index + array_size; + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), obj); + } else { + return_data->ForceSet(String::New(string_name), obj); + } + // Clean up memory allocation + free(string_name); + } + } + + // Check if we have a db reference + if(!is_array_item && return_data->Has(String::New("$ref")) && return_data->Has(String::New("$id"))) { + Handle dbrefValue = BSON::decodeDBref(bson, return_data->Get(String::New("$ref")), return_data->Get(String::New("$id")), return_data->Get(String::New("$db"))); + return scope.Close(dbrefValue); + } + + // Return the data object to javascript + if(is_array_item) { + return scope.Close(return_array); + } else { + return scope.Close(return_data); + } +} + +Handle BSON::BSONSerialize(const Arguments &args) { + HandleScope scope; + + if(args.Length() == 1 && !args[0]->IsObject()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 3 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean() && !args[3]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]"); + if(args.Length() > 4) return VException("One, two, tree or four arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]"); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + uint32_t object_size = 0; + // Calculate the total size of the document in binary form to ensure we only allocate memory once + // With serialize function + if(args.Length() == 4) { + object_size = BSON::calculate_object_size(bson, args[0], args[3]->BooleanValue()); + } else { + object_size = BSON::calculate_object_size(bson, args[0], false); + } + + // Allocate the memory needed for the serializtion + char *serialized_object = (char *)malloc(object_size * sizeof(char)); + // Catch any errors + try { + // Check if we have a boolean value + bool check_key = false; + if(args.Length() >= 3 && args[1]->IsBoolean()) { + check_key = args[1]->BooleanValue(); + } + + // Check if we have a boolean value + bool serializeFunctions = false; + if(args.Length() == 4 && args[1]->IsBoolean()) { + serializeFunctions = args[3]->BooleanValue(); + } + + // Serialize the object + BSON::serialize(bson, serialized_object, 0, Null(), args[0], check_key, serializeFunctions); + } catch(char *err_msg) { + // Free up serialized object space + free(serialized_object); + V8::AdjustAmountOfExternalAllocatedMemory(-object_size); + // Throw exception with the string + Handle error = VException(err_msg); + // free error message + free(err_msg); + // Return error + return error; + } + + // Write the object size + BSON::write_int32((serialized_object), object_size); + + // If we have 3 arguments + if(args.Length() == 3 || args.Length() == 4) { + // Local asBuffer = args[2]->ToBoolean(); + Buffer *buffer = Buffer::New(serialized_object, object_size); + // Release the serialized string + free(serialized_object); + return scope.Close(buffer->handle_); + } else { + // Encode the string (string - null termiating character) + Local bin_value = Encode(serialized_object, object_size, BINARY)->ToString(); + // Return the serialized content + return bin_value; + } +} + +Handle BSON::CalculateObjectSize(const Arguments &args) { + HandleScope scope; + // Ensure we have a valid object + if(args.Length() == 1 && !args[0]->IsObject()) return VException("One argument required - [object]"); + if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("Two arguments required - [object, boolean]"); + if(args.Length() > 3) return VException("One or two arguments required - [object] or [object, boolean]"); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // Object size + uint32_t object_size = 0; + // Check if we have our argument, calculate size of the object + if(args.Length() >= 2) { + object_size = BSON::calculate_object_size(bson, args[0], args[1]->BooleanValue()); + } else { + object_size = BSON::calculate_object_size(bson, args[0], false); + } + + // Return the object size + return scope.Close(Uint32::New(object_size)); +} + +uint32_t BSON::calculate_object_size(BSON *bson, Handle value, bool serializeFunctions) { + uint32_t object_size = 0; + + // If we have an object let's unwrap it and calculate the sub sections + if(value->IsString()) { + // Let's calculate the size the string adds, length + type(1 byte) + size(4 bytes) + object_size += value->ToString()->Utf8Length() + 1 + 4; + } else if(value->IsNumber()) { + // Check if we have a float value or a long value + Local number = value->ToNumber(); + double d_number = number->NumberValue(); + int64_t l_number = number->IntegerValue(); + // Check if we have a double value and not a int64 + double d_result = d_number - l_number; + // If we have a value after subtracting the integer value we have a float + if(d_result > 0 || d_result < 0) { + object_size = object_size + 8; + } else if(l_number <= BSON_INT32_MAX && l_number >= BSON_INT32_MIN) { + object_size = object_size + 4; + } else { + object_size = object_size + 8; + } + } else if(value->IsBoolean()) { + object_size = object_size + 1; + } else if(value->IsDate()) { + object_size = object_size + 8; + } else if(value->IsRegExp()) { + // Fetch the string for the regexp + Handle regExp = Handle::Cast(value); + ssize_t len = DecodeBytes(regExp->GetSource(), UTF8); + int flags = regExp->GetFlags(); + + // global + if((flags & (1 << 0)) != 0) len++; + // ignorecase + if((flags & (1 << 1)) != 0) len++; + //multiline + if((flags & (1 << 2)) != 0) len++; + // if((flags & (1 << 2)) != 0) len++; + // Calculate the space needed for the regexp: size of string - 2 for the /'ses +2 for null termiations + object_size = object_size + len + 2; + } else if(value->IsNull() || value->IsUndefined()) { + } else if(value->IsArray()) { + // Cast to array + Local array = Local::Cast(value->ToObject()); + // Turn length into string to calculate the size of all the strings needed + char *length_str = (char *)malloc(256 * sizeof(char)); + // Calculate the size of each element + for(uint32_t i = 0; i < array->Length(); i++) { + // Add "index" string size for each element + sprintf(length_str, "%d", i); + // Add the size of the string length + uint32_t label_length = strlen(length_str) + 1; + // Add the type definition size for each item + object_size = object_size + label_length + 1; + // Add size of the object + uint32_t object_length = BSON::calculate_object_size(bson, array->Get(Integer::New(i)), serializeFunctions); + object_size = object_size + object_length; + } + // Add the object size + object_size = object_size + 4 + 1; + // Free up memory + free(length_str); + } else if(value->IsFunction()) { + if(serializeFunctions) { + object_size += value->ToString()->Utf8Length() + 4 + 1; + } + } else if(value->ToObject()->Has(bson->_bsontypeString)) { + // Handle holder + Local constructorString = value->ToObject()->GetConstructorName(); + + // BSON type object, avoid non-needed checking unless we have a type + if(bson->longString->StrictEquals(constructorString)) { + object_size = object_size + 8; + } else if(bson->timestampString->StrictEquals(constructorString)) { + object_size = object_size + 8; + } else if(bson->objectIDString->StrictEquals(constructorString)) { + object_size = object_size + 12; + } else if(bson->binaryString->StrictEquals(constructorString)) { + // Unpack the object and encode + Local positionObj = value->ToObject()->Get(String::New("position"))->ToUint32(); + // Adjust the object_size, binary content lengt + total size int32 + binary size int32 + subtype + object_size += positionObj->Value() + 4 + 1; + } else if(bson->codeString->StrictEquals(constructorString)) { + // Unpack the object and encode + Local obj = value->ToObject(); + // Get the function + Local function = obj->Get(String::New("code"))->ToString(); + // Get the scope object + Local scope = obj->Get(String::New("scope"))->ToObject(); + + // For Node < 0.6.X use the GetPropertyNames + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + uint32_t propertyNameLength = scope->GetPropertyNames()->Length(); + #else + uint32_t propertyNameLength = scope->GetOwnPropertyNames()->Length(); + #endif + + // Check if the scope has any parameters + // Let's calculate the size the code object adds adds + if(propertyNameLength > 0) { + object_size += function->Utf8Length() + 4 + BSON::calculate_object_size(bson, scope, serializeFunctions) + 4 + 1; + } else { + object_size += function->Utf8Length() + 4 + 1; + } + } else if(bson->dbrefString->StrictEquals(constructorString)) { + // Unpack the dbref + Local dbref = value->ToObject(); + // Create an object containing the right namespace variables + Local obj = Object::New(); + // Build the new object + obj->Set(bson->_dbRefRefString, dbref->Get(bson->_dbRefNamespaceString)); + obj->Set(bson->_dbRefIdRefString, dbref->Get(bson->_dbRefOidString)); + if(!dbref->Get(bson->_dbRefDbString)->IsNull() && !dbref->Get(bson->_dbRefDbString)->IsUndefined()) obj->Set(bson->_dbRefDbRefString, dbref->Get(bson->_dbRefDbString)); + // Calculate size + object_size += BSON::calculate_object_size(bson, obj, serializeFunctions); + } else if(bson->minKeyString->StrictEquals(constructorString) || bson->maxKeyString->Equals(constructorString)) { + } else if(bson->symbolString->StrictEquals(constructorString)) { + // Get string + Local str = value->ToObject()->Get(String::New("value"))->ToString(); + // Get the utf8 length + int utf8_length = str->Utf8Length(); + // Check if we have a utf8 encoded string or not + if(utf8_length != str->Length()) { + // Let's calculate the size the string adds, length + type(1 byte) + size(4 bytes) + object_size += str->Utf8Length() + 1 + 4; + } else { + object_size += str->Length() + 1 + 4; + } + } else if(bson->doubleString->StrictEquals(constructorString)) { + object_size = object_size + 8; + } + } else if(value->IsObject()) { + // Unwrap the object + Local object = value->ToObject(); + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + Local property_names = object->GetPropertyNames(); + #else + Local property_names = object->GetOwnPropertyNames(); + #endif + + // Length of the property + uint32_t propertyLength = property_names->Length(); + + // Process all the properties on the object + for(uint32_t index = 0; index < propertyLength; index++) { + // Fetch the property name + Local property_name = property_names->Get(index)->ToString(); + + // Fetch the object for the property + Local property = object->Get(property_name); + // Get size of property (property + property name length + 1 for terminating 0) + if(!property->IsFunction() || (property->IsFunction() && serializeFunctions)) { + // Convert name to char* + object_size += BSON::calculate_object_size(bson, property, serializeFunctions) + property_name->Utf8Length() + 1 + 1; + } + } + + object_size = object_size + 4 + 1; + } + + return object_size; +} + +uint32_t BSON::serialize(BSON *bson, char *serialized_object, uint32_t index, Handle name, Handle value, bool check_key, bool serializeFunctions) { + // Scope for method execution + HandleScope scope; + + // If we have a name check that key is valid + if(!name->IsNull() && check_key) { + if(BSON::check_key(name->ToString()) != NULL) return -1; + } + + // If we have an object let's serialize it + if(value->IsString()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_STRING; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Write the actual string into the char array + Local str = value->ToString(); + // Let's fetch the int value + uint32_t utf8_length = str->Utf8Length(); + + // Write the integer to the char * + BSON::write_int32((serialized_object + index), utf8_length + 1); + // Adjust the index + index = index + 4; + // Write string to char in utf8 format + str->WriteUtf8((serialized_object + index), utf8_length); + // Add the null termination + *(serialized_object + index + utf8_length) = '\0'; + // Adjust the index + index = index + utf8_length + 1; + } else if(value->IsNumber()) { + uint32_t first_pointer = index; + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_INT; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + Local number = value->ToNumber(); + // Get the values + double d_number = number->NumberValue(); + int64_t l_number = number->IntegerValue(); + + // Check if we have a double value and not a int64 + double d_result = d_number - l_number; + // If we have a value after subtracting the integer value we have a float + if(d_result > 0 || d_result < 0) { + // Write the double to the char array + BSON::write_double((serialized_object + index), d_number); + // Adjust type to be double + *(serialized_object + first_pointer) = BSON_DATA_NUMBER; + // Adjust index for double + index = index + 8; + } else if(l_number <= BSON_INT32_MAX && l_number >= BSON_INT32_MIN) { + // Smaller than 32 bit, write as 32 bit value + BSON::write_int32(serialized_object + index, value->ToInt32()->Value()); + // Adjust the size of the index + index = index + 4; + } else if(l_number <= JS_INT_MAX && l_number >= JS_INT_MIN) { + // Write the double to the char array + BSON::write_double((serialized_object + index), d_number); + // Adjust type to be double + *(serialized_object + first_pointer) = BSON_DATA_NUMBER; + // Adjust index for double + index = index + 8; + } else { + BSON::write_double((serialized_object + index), d_number); + // Adjust type to be double + *(serialized_object + first_pointer) = BSON_DATA_NUMBER; + // Adjust the size of the index + index = index + 8; + } + } else if(value->IsBoolean()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_BOOLEAN; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Save the boolean value + *(serialized_object + index) = value->BooleanValue() ? '\1' : '\0'; + // Adjust the index + index = index + 1; + } else if(value->IsDate()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_DATE; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Fetch the Integer value + int64_t integer_value = value->IntegerValue(); + BSON::write_int64((serialized_object + index), integer_value); + // Adjust the index + index = index + 8; + } else if(value->IsNull() || value->IsUndefined()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_NULL; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + } else if(value->IsArray()) { + // Cast to array + Local array = Local::Cast(value->ToObject()); + // Turn length into string to calculate the size of all the strings needed + char *length_str = (char *)malloc(256 * sizeof(char)); + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_ARRAY; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + // Object size + uint32_t object_size = BSON::calculate_object_size(bson, value, serializeFunctions); + // Write the size of the object + BSON::write_int32((serialized_object + index), object_size); + // Adjust the index + index = index + 4; + // Write out all the elements + for(uint32_t i = 0; i < array->Length(); i++) { + // Add "index" string size for each element + sprintf(length_str, "%d", i); + // Encode the values + index = BSON::serialize(bson, serialized_object, index, String::New(length_str), array->Get(Integer::New(i)), check_key, serializeFunctions); + // Write trailing '\0' for object + *(serialized_object + index) = '\0'; + } + + // Pad the last item + *(serialized_object + index) = '\0'; + index = index + 1; + // Free up memory + free(length_str); + } else if(value->IsRegExp()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_REGEXP; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Fetch the string for the regexp + Handle regExp = Handle::Cast(value); + len = DecodeBytes(regExp->GetSource(), UTF8); + written = DecodeWrite((serialized_object + index), len, regExp->GetSource(), UTF8); + int flags = regExp->GetFlags(); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // global + if((flags & (1 << 0)) != 0) { + *(serialized_object + index) = 's'; + index = index + 1; + } + + // ignorecase + if((flags & (1 << 1)) != 0) { + *(serialized_object + index) = 'i'; + index = index + 1; + } + + //multiline + if((flags & (1 << 2)) != 0) { + *(serialized_object + index) = 'm'; + index = index + 1; + } + + // Add null termiation for the string + *(serialized_object + index) = '\0'; + // Adjust the index + index = index + 1; + } else if(value->IsFunction()) { + if(serializeFunctions) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_CODE; + + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Function String + Local function = value->ToString(); + + // Decode the function + len = DecodeBytes(function, BINARY); + // Write the size of the code string + 0 byte end of cString + BSON::write_int32((serialized_object + index), len + 1); + // Adjust the index + index = index + 4; + + // Write the data into the serialization stream + written = DecodeWrite((serialized_object + index), len, function, BINARY); + // Write \0 for string + *(serialized_object + index + len) = 0x00; + // Adjust the index + index = index + len + 1; + } + } else if(value->ToObject()->Has(bson->_bsontypeString)) { + // Handle holder + Local constructorString = value->ToObject()->GetConstructorName(); + uint32_t originalIndex = index; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + // Add null termiation for the string + *(serialized_object + index + len) = 0x00; + // Adjust the index + index = index + len + 1; + + // BSON type object, avoid non-needed checking unless we have a type + if(bson->longString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_LONG; + // Object reference + Local longObject = value->ToObject(); + + // Fetch the low and high bits + int32_t lowBits = longObject->Get(bson->_longLowString)->ToInt32()->Value(); + int32_t highBits = longObject->Get(bson->_longHighString)->ToInt32()->Value(); + + // Write the content to the char array + BSON::write_int32((serialized_object + index), lowBits); + BSON::write_int32((serialized_object + index + 4), highBits); + // Adjust the index + index = index + 8; + } else if(bson->timestampString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_TIMESTAMP; + // Object reference + Local timestampObject = value->ToObject(); + + // Fetch the low and high bits + int32_t lowBits = timestampObject->Get(bson->_longLowString)->ToInt32()->Value(); + int32_t highBits = timestampObject->Get(bson->_longHighString)->ToInt32()->Value(); + + // Write the content to the char array + BSON::write_int32((serialized_object + index), lowBits); + BSON::write_int32((serialized_object + index + 4), highBits); + // Adjust the index + index = index + 8; + } else if(bson->objectIDString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_OID; + // Convert to object + Local objectIDObject = value->ToObject(); + // Let's grab the id + Local idString = objectIDObject->Get(bson->_objectIDidString)->ToString(); + // Let's decode the raw chars from the string + len = DecodeBytes(idString, BINARY); + written = DecodeWrite((serialized_object + index), len, idString, BINARY); + // Adjust the index + index = index + 12; + } else if(bson->binaryString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_BINARY; + + // Let's get the binary object + Local binaryObject = value->ToObject(); + + // Grab the size(position of the binary) + uint32_t position = value->ToObject()->Get(bson->_binaryPositionString)->ToUint32()->Value(); + // Grab the subtype + uint32_t subType = value->ToObject()->Get(bson->_binarySubTypeString)->ToUint32()->Value(); + // Grab the buffer object + Local bufferObj = value->ToObject()->Get(bson->_binaryBufferString)->ToObject(); + + // Buffer data pointers + char *data; + uint32_t length; + + // Unpack the buffer variable + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(bufferObj); + data = buffer->data(); + length = buffer->length(); + #else + data = Buffer::Data(bufferObj); + length = Buffer::Length(bufferObj); + #endif + + // Write the size of the buffer out + BSON::write_int32((serialized_object + index), position); + // Adjust index + index = index + 4; + // Write subtype + *(serialized_object + index) = (char)subType; + // Adjust index + index = index + 1; + // Write binary content + memcpy((serialized_object + index), data, position); + // Adjust index.rar">_ + index = index + position; + } else if(bson->doubleString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_NUMBER; + + // Unpack the double + Local doubleObject = value->ToObject(); + + // Fetch the double value + Local doubleValue = doubleObject->Get(bson->_doubleValueString)->ToNumber(); + // Write the double to the char array + BSON::write_double((serialized_object + index), doubleValue->NumberValue()); + // Adjust index for double + index = index + 8; + } else if(bson->symbolString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_SYMBOL; + // Unpack symbol object + Local symbolObj = value->ToObject(); + + // Grab the actual string + Local str = symbolObj->Get(bson->_symbolValueString)->ToString(); + // Let's fetch the int value + int utf8_length = str->Utf8Length(); + + // If the Utf8 length is different from the string length then we + // have a UTF8 encoded string, otherwise write it as ascii + if(utf8_length != str->Length()) { + // Write the integer to the char * + BSON::write_int32((serialized_object + index), utf8_length + 1); + // Adjust the index + index = index + 4; + // Write string to char in utf8 format + str->WriteUtf8((serialized_object + index), utf8_length); + // Add the null termination + *(serialized_object + index + utf8_length) = '\0'; + // Adjust the index + index = index + utf8_length + 1; + } else { + // Write the integer to the char * + BSON::write_int32((serialized_object + index), str->Length() + 1); + // Adjust the index + index = index + 4; + // Write string to char in utf8 format + written = DecodeWrite((serialized_object + index), str->Length(), str, BINARY); + // Add the null termination + *(serialized_object + index + str->Length()) = '\0'; + // Adjust the index + index = index + str->Length() + 1; + } + } else if(bson->codeString->StrictEquals(constructorString)) { + // Unpack the object and encode + Local obj = value->ToObject(); + // Get the function + Local function = obj->Get(String::New("code"))->ToString(); + // Get the scope object + Local scope = obj->Get(String::New("scope"))->ToObject(); + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + uint32_t propertyNameLength = scope->GetPropertyNames()->Length(); + #else + uint32_t propertyNameLength = scope->GetOwnPropertyNames()->Length(); + #endif + + // Set the right type if we have a scope or not + if(propertyNameLength > 0) { + // Set basic data code object with scope object + *(serialized_object + originalIndex) = BSON_DATA_CODE_W_SCOPE; + + // Calculate the size of the whole object + uint32_t scopeSize = BSON::calculate_object_size(bson, scope, false); + // Decode the function length + ssize_t len = DecodeBytes(function, UTF8); + // Calculate total size + uint32_t size = 4 + len + 1 + 4 + scopeSize; + + // Write the total size + BSON::write_int32((serialized_object + index), size); + // Adjust the index + index = index + 4; + + // Write the function size + BSON::write_int32((serialized_object + index), len + 1); + // Adjust the index + index = index + 4; + + // Write the data into the serialization stream + ssize_t written = DecodeWrite((serialized_object + index), len, function, UTF8); + assert(written == len); + // Write \0 for string + *(serialized_object + index + len) = 0x00; + // Adjust the index with the length of the function + index = index + len + 1; + // Write the scope object + BSON::serialize(bson, (serialized_object + index), 0, Null(), scope, check_key, serializeFunctions); + // Adjust the index + index = index + scopeSize; + } else { + // Set basic data code object + *(serialized_object + originalIndex) = BSON_DATA_CODE; + // Decode the function + ssize_t len = DecodeBytes(function, BINARY); + // Write the size of the code string + 0 byte end of cString + BSON::write_int32((serialized_object + index), len + 1); + // Adjust the index + index = index + 4; + + // Write the data into the serialization stream + ssize_t written = DecodeWrite((serialized_object + index), len, function, BINARY); + assert(written == len); + // Write \0 for string + *(serialized_object + index + len) = 0x00; + // Adjust the index + index = index + len + 1; + } + } else if(bson->dbrefString->StrictEquals(constructorString)) { + // Unpack the dbref + Local dbref = value->ToObject(); + // Create an object containing the right namespace variables + Local obj = Object::New(); + + // Build the new object + obj->Set(bson->_dbRefRefString, dbref->Get(bson->_dbRefNamespaceString)); + obj->Set(bson->_dbRefIdRefString, dbref->Get(bson->_dbRefOidString)); + if(!dbref->Get(bson->_dbRefDbString)->IsNull() && !dbref->Get(bson->_dbRefDbString)->IsUndefined()) obj->Set(bson->_dbRefDbRefString, dbref->Get(bson->_dbRefDbString)); + + // Encode the variable + index = BSON::serialize(bson, serialized_object, originalIndex, name, obj, false, serializeFunctions); + } else if(bson->minKeyString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_MIN_KEY; + } else if(bson->maxKeyString->StrictEquals(constructorString)) { + *(serialized_object + originalIndex) = BSON_DATA_MAX_KEY; + } + } else if(value->IsObject()) { + if(!name->IsNull()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_OBJECT; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + } + + // Unwrap the object + Local object = value->ToObject(); + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + Local property_names = object->GetPropertyNames(); + #else + Local property_names = object->GetOwnPropertyNames(); + #endif + + // Calculate size of the total object + uint32_t object_size = BSON::calculate_object_size(bson, value, serializeFunctions); + // Write the size + BSON::write_int32((serialized_object + index), object_size); + // Adjust size + index = index + 4; + + // Process all the properties on the object + for(uint32_t i = 0; i < property_names->Length(); i++) { + // Fetch the property name + Local property_name = property_names->Get(i)->ToString(); + // Fetch the object for the property + Local property = object->Get(property_name); + // Write the next serialized object + // printf("========== !property->IsFunction() || (property->IsFunction() && serializeFunctions) = %d\n", !property->IsFunction() || (property->IsFunction() && serializeFunctions) == true ? 1 : 0); + if(!property->IsFunction() || (property->IsFunction() && serializeFunctions)) { + // Convert name to char* + ssize_t len = DecodeBytes(property_name, UTF8); + // char *data = new char[len]; + char *data = (char *)malloc(len + 1); + *(data + len) = '\0'; + ssize_t written = DecodeWrite(data, len, property_name, UTF8); + assert(written == len); + // Serialize the content + index = BSON::serialize(bson, serialized_object, index, property_name, property, check_key, serializeFunctions); + // Free up memory of data + free(data); + } + } + // Pad the last item + *(serialized_object + index) = '\0'; + index = index + 1; + + // Null out reminding fields if we have a toplevel object and nested levels + if(name->IsNull()) { + for(uint32_t i = 0; i < (object_size - index); i++) { + *(serialized_object + index + i) = '\0'; + } + } + } + + return index; +} + +Handle BSON::SerializeWithBufferAndIndex(const Arguments &args) { + HandleScope scope; + + //BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, checkKeys, buffer, index) { + // Ensure we have the correct values + if(args.Length() > 5) return VException("Four or five parameters required [object, boolean, Buffer, int] or [object, boolean, Buffer, int, boolean]"); + if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32()) return VException("Four parameters required [object, boolean, Buffer, int]"); + if(args.Length() == 5 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32() && !args[4]->IsBoolean()) return VException("Four parameters required [object, boolean, Buffer, int, boolean]"); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // Define pointer to data + char *data; + uint32_t length; + // Unpack the object + Local obj = args[2]->ToObject(); + + // Unpack the buffer object and get pointers to structures + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(obj); + data = buffer->data(); + length = buffer->length(); + #else + data = Buffer::Data(obj); + length = Buffer::Length(obj); + #endif + + uint32_t object_size = 0; + // Calculate the total size of the document in binary form to ensure we only allocate memory once + if(args.Length() == 5) { + object_size = BSON::calculate_object_size(bson, args[0], args[4]->BooleanValue()); + } else { + object_size = BSON::calculate_object_size(bson, args[0], false); + } + + // Unpack the index variable + Local indexObject = args[3]->ToUint32(); + uint32_t index = indexObject->Value(); + + // Allocate the memory needed for the serializtion + char *serialized_object = (char *)malloc(object_size * sizeof(char)); + + // Catch any errors + try { + // Check if we have a boolean value + bool check_key = false; + if(args.Length() >= 4 && args[1]->IsBoolean()) { + check_key = args[1]->BooleanValue(); + } + + bool serializeFunctions = false; + if(args.Length() == 5) { + serializeFunctions = args[4]->BooleanValue(); + } + + // Serialize the object + BSON::serialize(bson, serialized_object, 0, Null(), args[0], check_key, serializeFunctions); + } catch(char *err_msg) { + // Free up serialized object space + free(serialized_object); + V8::AdjustAmountOfExternalAllocatedMemory(-object_size); + // Throw exception with the string + Handle error = VException(err_msg); + // free error message + free(err_msg); + // Return error + return error; + } + + for(uint32_t i = 0; i < object_size; i++) { + *(data + index + i) = *(serialized_object + i); + } + + return scope.Close(Uint32::New(index + object_size - 1)); +} + +Handle BSON::BSONDeserializeStream(const Arguments &args) { + HandleScope scope; + + // At least 3 arguments required + if(args.Length() < 5) VException("Arguments required (Buffer(data), Number(index in data), Number(number of documents to deserialize), Array(results), Number(index in the array), Object(optional))"); + + // If the number of argumets equals 3 + if(args.Length() >= 5) { + if(!Buffer::HasInstance(args[0])) return VException("First argument must be Buffer instance"); + if(!args[1]->IsUint32()) return VException("Second argument must be a positive index number"); + if(!args[2]->IsUint32()) return VException("Third argument must be a positive number of documents to deserialize"); + if(!args[3]->IsArray()) return VException("Fourth argument must be an array the size of documents to deserialize"); + if(!args[4]->IsUint32()) return VException("Sixth argument must be a positive index number"); + } + + // If we have 4 arguments + if(args.Length() == 6 && !args[5]->IsObject()) return VException("Fifth argument must be an object with options"); + + // Define pointer to data + char *data; + uint32_t length; + Local obj = args[0]->ToObject(); + uint32_t numberOfDocuments = args[2]->ToUint32()->Value(); + uint32_t index = args[1]->ToUint32()->Value(); + uint32_t resultIndex = args[4]->ToUint32()->Value(); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // Unpack the buffer variable + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(obj); + data = buffer->data(); + length = buffer->length(); + #else + data = Buffer::Data(obj); + length = Buffer::Length(obj); + #endif + + // Fetch the documents + Local documents = args[3]->ToObject(); + + for(uint32_t i = 0; i < numberOfDocuments; i++) { + // Decode the size of the BSON data structure + uint32_t size = BSON::deserialize_int32(data, index); + + // Get result + Handle result = BSON::deserialize(bson, data, size, index, NULL); + + // Add result to array + documents->Set(i + resultIndex, result); + + // Adjust the index for next pass + index = index + size; + } + + // Return new index of parsing + return scope.Close(Uint32::New(index)); +} + +// Exporting function +extern "C" void init(Handle target) { + HandleScope scope; + BSON::Initialize(target); +} + +// NODE_MODULE(bson, BSON::Initialize); +// NODE_MODULE(l, Long::Initialize); diff --git a/node_modules/mongodb/external-libs/bson/bson.h b/node_modules/mongodb/external-libs/bson/bson.h new file mode 100644 index 0000000..dcf21d1 --- /dev/null +++ b/node_modules/mongodb/external-libs/bson/bson.h @@ -0,0 +1,105 @@ +#ifndef BSON_H_ +#define BSON_H_ + +#include +#include +#include + +using namespace v8; +using namespace node; + +class BSON : public ObjectWrap { + public: + BSON() : ObjectWrap() {} + ~BSON() {} + + static void Initialize(Handle target); + static Handle BSONDeserializeStream(const Arguments &args); + + // JS based objects + static Handle BSONSerialize(const Arguments &args); + static Handle BSONDeserialize(const Arguments &args); + + // Calculate size of function + static Handle CalculateObjectSize(const Arguments &args); + static Handle SerializeWithBufferAndIndex(const Arguments &args); + + // Experimental + static Handle CalculateObjectSize2(const Arguments &args); + static Handle BSONSerialize2(const Arguments &args); + + // Constructor used for creating new BSON objects from C++ + static Persistent constructor_template; + + private: + static Handle New(const Arguments &args); + static Handle deserialize(BSON *bson, char *data, uint32_t dataLength, uint32_t startIndex, bool is_array_item); + static uint32_t serialize(BSON *bson, char *serialized_object, uint32_t index, Handle name, Handle value, bool check_key, bool serializeFunctions); + + static char* extract_string(char *data, uint32_t offset); + static const char* ToCString(const v8::String::Utf8Value& value); + static uint32_t calculate_object_size(BSON *bson, Handle object, bool serializeFunctions); + + static void write_int32(char *data, uint32_t value); + static void write_int64(char *data, int64_t value); + static void write_double(char *data, double value); + static uint16_t deserialize_int8(char *data, uint32_t offset); + static uint32_t deserialize_int32(char* data, uint32_t offset); + static char *check_key(Local key); + + // BSON type instantiate functions + Persistent longConstructor; + Persistent objectIDConstructor; + Persistent binaryConstructor; + Persistent codeConstructor; + Persistent dbrefConstructor; + Persistent symbolConstructor; + Persistent doubleConstructor; + Persistent timestampConstructor; + Persistent minKeyConstructor; + Persistent maxKeyConstructor; + + // Equality Objects + Persistent longString; + Persistent objectIDString; + Persistent binaryString; + Persistent codeString; + Persistent dbrefString; + Persistent symbolString; + Persistent doubleString; + Persistent timestampString; + Persistent minKeyString; + Persistent maxKeyString; + + // Equality speed up comparision objects + Persistent _bsontypeString; + Persistent _longLowString; + Persistent _longHighString; + Persistent _objectIDidString; + Persistent _binaryPositionString; + Persistent _binarySubTypeString; + Persistent _binaryBufferString; + Persistent _doubleValueString; + Persistent _symbolValueString; + + Persistent _dbRefRefString; + Persistent _dbRefIdRefString; + Persistent _dbRefDbRefString; + Persistent _dbRefNamespaceString; + Persistent _dbRefDbString; + Persistent _dbRefOidString; + + // Decode JS function + static Handle decodeLong(BSON *bson, char *data, uint32_t index); + static Handle decodeTimestamp(BSON *bson, char *data, uint32_t index); + static Handle decodeOid(BSON *bson, char *oid); + static Handle decodeBinary(BSON *bson, uint32_t sub_type, uint32_t number_of_bytes, char *data); + static Handle decodeCode(BSON *bson, char *code, Handle scope); + static Handle decodeDBref(BSON *bson, Local ref, Local oid, Local db); + + // Experimental + static uint32_t calculate_object_size2(Handle object); + static uint32_t serialize2(char *serialized_object, uint32_t index, Handle name, Handle value, uint32_t object_size, bool check_key); +}; + +#endif // BSON_H_ diff --git a/node_modules/mongodb/external-libs/bson/index.js b/node_modules/mongodb/external-libs/bson/index.js new file mode 100644 index 0000000..2c66dee --- /dev/null +++ b/node_modules/mongodb/external-libs/bson/index.js @@ -0,0 +1,20 @@ +var bson = require('./bson'); +exports.BSON = bson.BSON; +exports.Long = require('../../lib/mongodb/bson/long').Long; +exports.ObjectID = require('../../lib/mongodb/bson/objectid').ObjectID; +exports.DBRef = require('../../lib/mongodb/bson/db_ref').DBRef; +exports.Code = require('../../lib/mongodb/bson/code').Code; +exports.Timestamp = require('../../lib/mongodb/bson/timestamp').Timestamp; +exports.Binary = require('../../lib/mongodb/bson/binary').Binary; +exports.Double = require('../../lib/mongodb/bson/double').Double; +exports.MaxKey = require('../../lib/mongodb/bson/max_key').MaxKey; +exports.MinKey = require('../../lib/mongodb/bson/min_key').MinKey; +exports.Symbol = require('../../lib/mongodb/bson/symbol').Symbol; + +// Just add constants tot he Native BSON parser +exports.BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0; +exports.BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1; +exports.BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +exports.BSON.BSON_BINARY_SUBTYPE_UUID = 3; +exports.BSON.BSON_BINARY_SUBTYPE_MD5 = 4; +exports.BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; diff --git a/node_modules/mongodb/external-libs/bson/test/test_bson.js b/node_modules/mongodb/external-libs/bson/test/test_bson.js new file mode 100644 index 0000000..706f1df --- /dev/null +++ b/node_modules/mongodb/external-libs/bson/test/test_bson.js @@ -0,0 +1,349 @@ +var sys = require('util'), + debug = require('util').debug, + inspect = require('util').inspect, + Buffer = require('buffer').Buffer, + BSON = require('../bson').BSON, + Buffer = require('buffer').Buffer, + BSONJS = require('../../../lib/mongodb/bson/bson').BSON, + BinaryParser = require('../../../lib/mongodb/bson/binary_parser').BinaryParser, + Long = require('../../../lib/mongodb/bson/long').Long, + ObjectID = require('../../../lib/mongodb/bson/bson').ObjectID, + Binary = require('../../../lib/mongodb/bson/bson').Binary, + Code = require('../../../lib/mongodb/bson/bson').Code, + DBRef = require('../../../lib/mongodb/bson/bson').DBRef, + Symbol = require('../../../lib/mongodb/bson/bson').Symbol, + Double = require('../../../lib/mongodb/bson/bson').Double, + MaxKey = require('../../../lib/mongodb/bson/bson').MaxKey, + MinKey = require('../../../lib/mongodb/bson/bson').MinKey, + Timestamp = require('../../../lib/mongodb/bson/bson').Timestamp, + assert = require('assert'); + +if(process.env['npm_package_config_native'] != null) return; + +sys.puts("=== EXECUTING TEST_BSON ==="); + +// Should fail due to illegal key +assert.throws(function() { new ObjectID('foo'); }) +assert.throws(function() { new ObjectID('foo'); }) + +// Parsers +var bsonC = new BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); +var bsonJS = new BSONJS([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); + +// Simple serialization and deserialization of edge value +var doc = {doc:0x1ffffffffffffe}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +var doc = {doc:-0x1ffffffffffffe}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// +// Assert correct toJSON +// +var a = Long.fromNumber(10); +assert.equal(10, a); + +var a = Long.fromNumber(9223372036854775807); +assert.equal(9223372036854775807, a); + +// Simple serialization and deserialization test for a Single String value +var doc = {doc:'Serialize'}; +var simple_string_serialized = bsonC.serialize(doc, true, false); + +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// Nested doc +var doc = {a:{b:{c:1}}}; +var simple_string_serialized = bsonC.serialize(doc, false, true); + +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// Simple integer serialization/deserialization test, including testing boundary conditions +var doc = {doc:-1}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +var doc = {doc:2147483648}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +var doc = {doc:-2147483648}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// Simple serialization and deserialization test for a Long value +var doc = {doc:Long.fromNumber(9223372036854775807)}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize({doc:Long.fromNumber(9223372036854775807)}, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +var doc = {doc:Long.fromNumber(-9223372036854775807)}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize({doc:Long.fromNumber(-9223372036854775807)}, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// Simple serialization and deserialization for a Float value +var doc = {doc:2222.3333}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +var doc = {doc:-2222.3333}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// Simple serialization and deserialization for a null value +var doc = {doc:null}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// Simple serialization and deserialization for a boolean value +var doc = {doc:true}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// Simple serialization and deserialization for a date value +var date = new Date(); +var doc = {doc:date}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + +// Simple serialization and deserialization for a boolean value +var doc = {doc:/abcd/mi}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.equal(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString(), bsonC.deserialize(simple_string_serialized).doc.toString()); + +var doc = {doc:/abcd/}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); +assert.equal(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString(), bsonC.deserialize(simple_string_serialized).doc.toString()); + +// Simple serialization and deserialization for a objectId value +var doc = {doc:new ObjectID()}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +var doc2 = {doc:ObjectID.createFromHexString(doc.doc.toHexString())}; + +assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc2, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString(), bsonC.deserialize(simple_string_serialized).doc.toString()); + +// Simple serialization and deserialization for a Binary value +var binary = new Binary(); +var string = 'binstring' +for(var index = 0; index < string.length; index++) { binary.put(string.charAt(index)); } + +var Binary = new Binary(); +var string = 'binstring' +for(var index = 0; index < string.length; index++) { Binary.put(string.charAt(index)); } + +var simple_string_serialized = bsonC.serialize({doc:binary}, false, true); +assert.deepEqual(simple_string_serialized, bsonJS.serialize({doc:Binary}, false, true)); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.value(), bsonC.deserialize(simple_string_serialized).doc.value()); + +// Simple serialization and deserialization for a Code value +var code = new Code('this.a > i', {'i': 1}); +var Code = new Code('this.a > i', {'i': 1}); +var simple_string_serialized_2 = bsonJS.serialize({doc:Code}, false, true); +var simple_string_serialized = bsonC.serialize({doc:code}, false, true); + +assert.deepEqual(simple_string_serialized, simple_string_serialized_2); +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')).doc.scope, bsonC.deserialize(simple_string_serialized).doc.scope); + +// Simple serialization and deserialization for an Object +var simple_string_serialized = bsonC.serialize({doc:{a:1, b:{c:2}}}, false, true); +var simple_string_serialized_2 = bsonJS.serialize({doc:{a:1, b:{c:2}}}, false, true); +assert.deepEqual(simple_string_serialized, simple_string_serialized_2) +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')).doc, bsonC.deserialize(simple_string_serialized).doc); + +// Simple serialization and deserialization for an Array +var simple_string_serialized = bsonC.serialize({doc:[9, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1]}, false, true); +var simple_string_serialized_2 = bsonJS.serialize({doc:[9, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1]}, false, true); + +assert.deepEqual(simple_string_serialized, simple_string_serialized_2) +assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')).doc, bsonC.deserialize(simple_string_serialized).doc); + +// Simple serialization and deserialization for a DBRef +var oid = new ObjectID() +var oid2 = new ObjectID.createFromHexString(oid.toHexString()) +var simple_string_serialized = bsonJS.serialize({doc:new DBRef('namespace', oid2, 'integration_tests_')}, false, true); +var simple_string_serialized_2 = bsonC.serialize({doc:new DBRef('namespace', oid, 'integration_tests_')}, false, true); + +assert.deepEqual(simple_string_serialized, simple_string_serialized_2) +// Ensure we have the same values for the dbref +var object_js = bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')); +var object_c = bsonC.deserialize(simple_string_serialized); + +assert.equal(object_js.doc.namespace, object_c.doc.namespace); +assert.equal(object_js.doc.oid.toHexString(), object_c.doc.oid.toHexString()); +assert.equal(object_js.doc.db, object_c.doc.db); + +// Serialized document +var bytes = [47,0,0,0,2,110,97,109,101,0,6,0,0,0,80,97,116,116,121,0,16,97,103,101,0,34,0,0,0,7,95,105,100,0,76,100,12,23,11,30,39,8,89,0,0,1,0]; +var serialized_data = ''; +// Convert to chars +for(var i = 0; i < bytes.length; i++) { + serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); +} +var object = bsonC.deserialize(new Buffer(serialized_data, 'binary')); +assert.equal('Patty', object.name) +assert.equal(34, object.age) +assert.equal('4c640c170b1e270859000001', object._id.toHexString()) + +// Serialize utf8 +var doc = { "name" : "本荘由利地域に洪水警報", "name1" : "öüóőúéáűíÖÜÓŐÚÉÁŰÍ", "name2" : "abcdedede"}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +var simple_string_serialized2 = bsonJS.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, simple_string_serialized2) + +var object = bsonC.deserialize(simple_string_serialized); +assert.equal(doc.name, object.name) +assert.equal(doc.name1, object.name1) +assert.equal(doc.name2, object.name2) + +// Serialize object with array +var doc = {b:[1, 2, 3]}; +var simple_string_serialized = bsonC.serialize(doc, false, true); +var simple_string_serialized_2 = bsonJS.serialize(doc, false, true); +assert.deepEqual(simple_string_serialized, simple_string_serialized_2) + +var object = bsonC.deserialize(simple_string_serialized); +assert.deepEqual(doc, object) + +// Test equality of an object ID +var object_id = new ObjectID(); +var object_id_2 = new ObjectID(); +assert.ok(object_id.equals(object_id)); +assert.ok(!(object_id.equals(object_id_2))) + +// Test same serialization for Object ID +var object_id = new ObjectID(); +var object_id2 = ObjectID.createFromHexString(object_id.toString()) +var simple_string_serialized = bsonJS.serialize({doc:object_id}, false, true); +var simple_string_serialized_2 = bsonC.serialize({doc:object_id2}, false, true); + +assert.equal(simple_string_serialized_2.length, simple_string_serialized.length); +assert.deepEqual(simple_string_serialized, simple_string_serialized_2) +var object = bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')); +var object2 = bsonC.deserialize(simple_string_serialized); +assert.equal(object.doc.id, object2.doc.id) + +// JS Object +var c1 = { _id: new ObjectID, comments: [], title: 'number 1' }; +var c2 = { _id: new ObjectID, comments: [], title: 'number 2' }; +var doc = { + numbers: [] + , owners: [] + , comments: [c1, c2] + , _id: new ObjectID +}; + +var simple_string_serialized = bsonJS.serialize(doc, false, true); + +// C++ Object +var c1 = { _id: ObjectID.createFromHexString(c1._id.toHexString()), comments: [], title: 'number 1' }; +var c2 = { _id: ObjectID.createFromHexString(c2._id.toHexString()), comments: [], title: 'number 2' }; +var doc = { + numbers: [] + , owners: [] + , comments: [c1, c2] + , _id: ObjectID.createFromHexString(doc._id.toHexString()) +}; + +var simple_string_serialized_2 = bsonC.serialize(doc, false, true); + +for(var i = 0; i < simple_string_serialized_2.length; i++) { + // debug(i + "[" + simple_string_serialized_2[i] + "] = [" + simple_string_serialized[i] + "]") + assert.equal(simple_string_serialized_2[i], simple_string_serialized[i]); +} + +// Deserialize the string +var doc1 = bsonJS.deserialize(new Buffer(simple_string_serialized_2)); +var doc2 = bsonC.deserialize(new Buffer(simple_string_serialized_2)); +assert.equal(doc._id.id, doc1._id.id) +assert.equal(doc._id.id, doc2._id.id) +assert.equal(doc1._id.id, doc2._id.id) + +var doc = { + _id: 'testid', + key1: { code: 'test1', time: {start:1309323402727,end:1309323402727}, x:10, y:5 }, + key2: { code: 'test1', time: {start:1309323402727,end:1309323402727}, x:10, y:5 } +}; + +var simple_string_serialized = bsonJS.serialize(doc, false, true); +var simple_string_serialized_2 = bsonC.serialize(doc, false, true); + +// Deserialize the string +var doc1 = bsonJS.deserialize(new Buffer(simple_string_serialized_2)); +var doc2 = bsonC.deserialize(new Buffer(simple_string_serialized_2)); +assert.deepEqual(doc2, doc1) +assert.deepEqual(doc, doc2) +assert.deepEqual(doc, doc1) + +// Serialize function +var doc = { + _id: 'testid', + key1: function() {} +} + +var simple_string_serialized = bsonJS.serialize(doc, false, true, true); +var simple_string_serialized_2 = bsonC.serialize(doc, false, true, true); + +// Deserialize the string +var doc1 = bsonJS.deserialize(new Buffer(simple_string_serialized_2)); +var doc2 = bsonC.deserialize(new Buffer(simple_string_serialized_2)); +assert.equal(doc1.key1.code.toString(), doc2.key1.code.toString()) + +var doc = {"user_id":"4e9fc8d55883d90100000003","lc_status":{"$ne":"deleted"},"owner_rating":{"$exists":false}}; +var simple_string_serialized = bsonJS.serialize(doc, false, true, true); +var simple_string_serialized_2 = bsonC.serialize(doc, false, true, true); + +// Should serialize to the same value +assert.equal(simple_string_serialized_2.toString('base64'), simple_string_serialized.toString('base64')) +var doc1 = bsonJS.deserialize(simple_string_serialized_2); +var doc2 = bsonC.deserialize(simple_string_serialized); +assert.deepEqual(doc1, doc2) + +// Hex Id +var hexId = new ObjectID().toString(); +var docJS = {_id: ObjectID.createFromHexString(hexId), 'funds.remaining': {$gte: 1.222}, 'transactions.id': {$ne: ObjectID.createFromHexString(hexId)}}; +var docC = {_id: ObjectID.createFromHexString(hexId), 'funds.remaining': {$gte: 1.222}, 'transactions.id': {$ne: ObjectID.createFromHexString(hexId)}}; +var docJSBin = bsonJS.serialize(docJS, false, true, true); +var docCBin = bsonC.serialize(docC, false, true, true); +assert.equal(docCBin.toString('base64'), docJSBin.toString('base64')); + +// // Complex document serialization +// doc = {"DateTime": "Tue Nov 40 2011 17:27:55 GMT+0000 (WEST)","isActive": true,"Media": {"URL": "http://videos.sapo.pt/Tc85NsjaKjj8o5aV7Ubb"},"Title": "Lisboa fecha a ganhar 0.19%","SetPosition": 60,"Type": "videos","Thumbnail": [{"URL": "http://rd3.videos.sapo.pt/Tc85NsjaKjj8o5aV7Ubb/pic/320x240","Dimensions": {"Height": 240,"Width": 320}}],"Source": {"URL": "http://videos.sapo.pt","SetID": "1288","SourceID": "http://videos.sapo.pt/tvnet/rss2","SetURL": "http://noticias.sapo.pt/videos/tv-net_1288/","ItemID": "Tc85NsjaKjj8o5aV7Ubb","Name": "SAPO Vídeos"},"Category": "Tec_ciencia","Description": "Lisboa fecha a ganhar 0.19%","GalleryID": new ObjectID("4eea2a634ce8573200000000"),"InternalRefs": {"RegisterDate": "Thu Dec 15 2011 17:12:51 GMT+0000 (WEST)","ChangeDate": "Thu Dec 15 2011 17:12:51 GMT+0000 (WEST)","Hash": 332279244514},"_id": new ObjectID("4eea2a96e52778160000003a")} +// var docJSBin = bsonJS.serialize(docJS, false, true, true); +// var docCBin = bsonC.serialize(docC, false, true, true); +// +// + +// // Force garbage collect +// global.gc(); + + + + + + + + + + + + + + + diff --git a/node_modules/mongodb/external-libs/bson/test/test_full_bson.js b/node_modules/mongodb/external-libs/bson/test/test_full_bson.js new file mode 100644 index 0000000..2a6506c --- /dev/null +++ b/node_modules/mongodb/external-libs/bson/test/test_full_bson.js @@ -0,0 +1,218 @@ +var sys = require('util'), + fs = require('fs'), + Buffer = require('buffer').Buffer, + BSON = require('../bson').BSON, + Buffer = require('buffer').Buffer, + assert = require('assert'), + BinaryParser = require('../../../lib/mongodb/bson/binary_parser').BinaryParser, + BSONJS = require('../../../lib/mongodb/bson/bson').BSON, + Long = require('../../../lib/mongodb/bson/long').Long, + ObjectID = require('../../../lib/mongodb/bson/bson').ObjectID, + Binary = require('../../../lib/mongodb/bson/bson').Binary, + Code = require('../../../lib/mongodb/bson/bson').Code, + DBRef = require('../../../lib/mongodb/bson/bson').DBRef, + Symbol = require('../../../lib/mongodb/bson/bson').Symbol, + Double = require('../../../lib/mongodb/bson/bson').Double, + MaxKey = require('../../../lib/mongodb/bson/bson').MaxKey, + MinKey = require('../../../lib/mongodb/bson/bson').MinKey, + Timestamp = require('../../../lib/mongodb/bson/bson').Timestamp; + +if(process.env['npm_package_config_native'] != null) return; + +sys.puts("=== EXECUTING TEST_FULL_BSON ==="); + +// Parsers +var bsonC = new BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); +var bsonJS = new BSONJS([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); + +// Should Correctly Deserialize object +var bytes = [95,0,0,0,2,110,115,0,42,0,0,0,105,110,116,101,103,114,97,116,105,111,110,95,116,101,115,116,115,95,46,116,101,115,116,95,105,110,100,101,120,95,105,110,102,111,114,109,97,116,105,111,110,0,8,117,110,105,113,117,101,0,0,3,107,101,121,0,12,0,0,0,16,97,0,1,0,0,0,0,2,110,97,109,101,0,4,0,0,0,97,95,49,0,0]; +var serialized_data = ''; +// Convert to chars +for(var i = 0; i < bytes.length; i++) { + serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); +} +var object = bsonC.deserialize(serialized_data); +assert.equal("a_1", object.name); +assert.equal(false, object.unique); +assert.equal(1, object.key.a); + +// Should Correctly Deserialize object with all types +var bytes = [26,1,0,0,7,95,105,100,0,161,190,98,75,118,169,3,0,0,3,0,0,4,97,114,114,97,121,0,26,0,0,0,16,48,0,1,0,0,0,16,49,0,2,0,0,0,16,50,0,3,0,0,0,0,2,115,116,114,105,110,103,0,6,0,0,0,104,101,108,108,111,0,3,104,97,115,104,0,19,0,0,0,16,97,0,1,0,0,0,16,98,0,2,0,0,0,0,9,100,97,116,101,0,161,190,98,75,0,0,0,0,7,111,105,100,0,161,190,98,75,90,217,18,0,0,1,0,0,5,98,105,110,97,114,121,0,7,0,0,0,2,3,0,0,0,49,50,51,16,105,110,116,0,42,0,0,0,1,102,108,111,97,116,0,223,224,11,147,169,170,64,64,11,114,101,103,101,120,112,0,102,111,111,98,97,114,0,105,0,8,98,111,111,108,101,97,110,0,1,15,119,104,101,114,101,0,25,0,0,0,12,0,0,0,116,104,105,115,46,120,32,61,61,32,51,0,5,0,0,0,0,3,100,98,114,101,102,0,37,0,0,0,2,36,114,101,102,0,5,0,0,0,116,101,115,116,0,7,36,105,100,0,161,190,98,75,2,180,1,0,0,2,0,0,0,10,110,117,108,108,0,0]; +var serialized_data = ''; +// Convert to chars +for(var i = 0; i < bytes.length; i++) { + serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); +} + +var object = bsonJS.deserialize(new Buffer(serialized_data, 'binary')); +assert.equal("hello", object.string); +assert.deepEqual([1, 2, 3], object.array); +assert.equal(1, object.hash.a); +assert.equal(2, object.hash.b); +assert.ok(object.date != null); +assert.ok(object.oid != null); +assert.ok(object.binary != null); +assert.equal(42, object.int); +assert.equal(33.3333, object.float); +assert.ok(object.regexp != null); +assert.equal(true, object.boolean); +assert.ok(object.where != null); +assert.ok(object.dbref != null); +assert.ok(object['null'] == null); + +// Should Serialize and Deserialze String +var test_string = {hello: 'world'} +var serialized_data = bsonC.serialize(test_string) +assert.deepEqual(test_string, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize Integer +var test_number = {doc: 5} +var serialized_data = bsonC.serialize(test_number) +assert.deepEqual(test_number, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize null value +var test_null = {doc:null} +var serialized_data = bsonC.serialize(test_null) +var object = bsonC.deserialize(serialized_data); +assert.deepEqual(test_null, object); + +// Should Correctly Serialize and Deserialize undefined value +var test_undefined = {doc:undefined} +var serialized_data = bsonC.serialize(test_undefined) +var object = bsonJS.deserialize(new Buffer(serialized_data, 'binary')); +assert.equal(null, object.doc) + +// Should Correctly Serialize and Deserialize Number +var test_number = {doc: 5.5} +var serialized_data = bsonC.serialize(test_number) +assert.deepEqual(test_number, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize Integer +var test_int = {doc: 42} +var serialized_data = bsonC.serialize(test_int) +assert.deepEqual(test_int, bsonC.deserialize(serialized_data)); + +test_int = {doc: -5600} +serialized_data = bsonC.serialize(test_int) +assert.deepEqual(test_int, bsonC.deserialize(serialized_data)); + +test_int = {doc: 2147483647} +serialized_data = bsonC.serialize(test_int) +assert.deepEqual(test_int, bsonC.deserialize(serialized_data)); + +test_int = {doc: -2147483648} +serialized_data = bsonC.serialize(test_int) +assert.deepEqual(test_int, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize Object +var doc = {doc: {age: 42, name: 'Spongebob', shoe_size: 9.5}} +var serialized_data = bsonC.serialize(doc) +assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize Array +var doc = {doc: [1, 2, 'a', 'b']} +var serialized_data = bsonC.serialize(doc) +assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize Array with added on functions +var doc = {doc: [1, 2, 'a', 'b']} +var serialized_data = bsonC.serialize(doc) +assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize A Boolean +var doc = {doc: true} +var serialized_data = bsonC.serialize(doc) +assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize a Date +var date = new Date() +//(2009, 11, 12, 12, 00, 30) +date.setUTCDate(12) +date.setUTCFullYear(2009) +date.setUTCMonth(11 - 1) +date.setUTCHours(12) +date.setUTCMinutes(0) +date.setUTCSeconds(30) +var doc = {doc: date} +var serialized_data = bsonC.serialize(doc) +assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + +// // Should Correctly Serialize and Deserialize Oid +var doc = {doc: new ObjectID()} +var serialized_data = bsonC.serialize(doc) +assert.deepEqual(doc.doc.toHexString(), bsonC.deserialize(serialized_data).doc.toHexString()) + +// Should Correctly encode Empty Hash +var test_code = {} +var serialized_data = bsonC.serialize(test_code) +assert.deepEqual(test_code, bsonC.deserialize(serialized_data)); + +// Should Correctly Serialize and Deserialize Ordered Hash +var doc = {doc: {b:1, a:2, c:3, d:4}} +var serialized_data = bsonC.serialize(doc) +var decoded_hash = bsonC.deserialize(serialized_data).doc +var keys = [] +for(name in decoded_hash) keys.push(name) +assert.deepEqual(['b', 'a', 'c', 'd'], keys) + +// Should Correctly Serialize and Deserialize Regular Expression +// Serialize the regular expression +var doc = {doc: /foobar/mi} +var serialized_data = bsonC.serialize(doc) +var doc2 = bsonC.deserialize(serialized_data); +assert.equal(doc.doc.toString(), doc2.doc.toString()) + +// Should Correctly Serialize and Deserialize a Binary object +var bin = new Binary() +var string = 'binstring' +for(var index = 0; index < string.length; index++) { + bin.put(string.charAt(index)) +} +var doc = {doc: bin} +var serialized_data = bsonC.serialize(doc) +var deserialized_data = bsonC.deserialize(serialized_data); +assert.equal(doc.doc.value(), deserialized_data.doc.value()) + +// Should Correctly Serialize and Deserialize a big Binary object +var data = fs.readFileSync("../../test/gridstore/test_gs_weird_bug.png", 'binary'); +var bin = new Binary() +bin.write(data) +var doc = {doc: bin} +var serialized_data = bsonC.serialize(doc) +var deserialized_data = bsonC.deserialize(serialized_data); +assert.equal(doc.doc.value(), deserialized_data.doc.value()) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/node_modules/mongodb/external-libs/bson/test/test_stackless_bson.js b/node_modules/mongodb/external-libs/bson/test/test_stackless_bson.js new file mode 100644 index 0000000..f271cac --- /dev/null +++ b/node_modules/mongodb/external-libs/bson/test/test_stackless_bson.js @@ -0,0 +1,132 @@ +var Buffer = require('buffer').Buffer, + BSON = require('../bson').BSON, + Buffer = require('buffer').Buffer, + BSONJS = require('../../../lib/mongodb/bson/bson').BSON, + BinaryParser = require('../../../lib/mongodb/bson/binary_parser').BinaryParser, + Long = require('../../../lib/mongodb/bson/long').Long, + ObjectID = require('../../../lib/mongodb/bson/bson').ObjectID, + Binary = require('../../../lib/mongodb/bson/bson').Binary, + Code = require('../../../lib/mongodb/bson/bson').Code, + DBRef = require('../../../lib/mongodb/bson/bson').DBRef, + Symbol = require('../../../lib/mongodb/bson/bson').Symbol, + Double = require('../../../lib/mongodb/bson/bson').Double, + MaxKey = require('../../../lib/mongodb/bson/bson').MaxKey, + MinKey = require('../../../lib/mongodb/bson/bson').MinKey, + Timestamp = require('../../../lib/mongodb/bson/bson').Timestamp; + assert = require('assert'); + +if(process.env['npm_package_config_native'] != null) return; + +console.log("=== EXECUTING TEST_STACKLESS_BSON ==="); + +// Parsers +var bsonC = new BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); +var bsonJS = new BSONJS([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); + +// Number of iterations for the benchmark +var COUNT = 10000; +// var COUNT = 1; +// Sample simple doc +var doc = {key:"Hello world", key2:"šđžčćŠĐŽČĆ", key3:'客家话', key4:'how are you doing dog!!'}; +// var doc = {}; +// for(var i = 0; i < 100; i++) { +// doc['string' + i] = "dumdyms fsdfdsfdsfdsfsdfdsfsdfsdfsdfsdfsdfsdfsdffsfsdfs"; +// } + +// // Calculate size +console.log(bsonC.calculateObjectSize2(doc)); +console.log(bsonJS.calculateObjectSize(doc)); +// assert.equal(bsonJS.calculateObjectSize(doc), bsonC.calculateObjectSize2(doc)); + +// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- +// Benchmark calculateObjectSize +// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- + +// Benchmark 1 JS BSON +console.log(COUNT + "x (objectBSON = bsonC.calculateObjectSize(object))") +start = new Date + +for (j=COUNT; --j>=0; ) { + var objectBSON = bsonJS.calculateObjectSize(doc); +} + +end = new Date +var opsprsecond = COUNT / ((end - start)/1000); +console.log("time = ", end - start, "ms -", COUNT / ((end - start)/1000), " ops/sec"); + +// Benchmark 2 C++ BSON calculateObjectSize +console.log(COUNT + "x (objectBSON = bsonC.calculateObjectSize(object))") +start = new Date + +for (j=COUNT; --j>=0; ) { + var objectBSON = bsonC.calculateObjectSize(doc); +} + +end = new Date +var opsprsecond = COUNT / ((end - start)/1000); +console.log("time = ", end - start, "ms -", COUNT / ((end - start)/1000), " ops/sec"); + +// Benchmark 3 C++ BSON calculateObjectSize2 +console.log(COUNT + "x (objectBSON = bsonC.calculateObjectSize2(object))") +start = new Date + +for (j=COUNT; --j>=0; ) { + var objectBSON = bsonC.calculateObjectSize2(doc); +} + +end = new Date +var opsprsecond = COUNT / ((end - start)/1000); +console.log("time = ", end - start, "ms -", COUNT / ((end - start)/1000), " ops/sec"); + +// // Serialize the content +// var _serializedDoc1 = bsonJS.serialize(doc, true, false); +// var _serializedDoc2 = bsonC.serialize2(doc, true, false); +// console.dir(_serializedDoc1); +// console.dir(_serializedDoc2); +// assert.equal(_serializedDoc1.toString('base64'), _serializedDoc2.toString('base64')) +// +// +// // Benchmark 1 +// console.log(COUNT + "x (objectBSON = bsonC.serialize(object))") +// start = new Date +// +// for (j=COUNT; --j>=0; ) { +// // var objectBSON = bsonC.serialize2(doc, true, false); +// var objectBSON = bsonJS.serialize(doc, true, false); +// } +// +// end = new Date +// var opsprsecond = COUNT / ((end - start)/1000); +// console.log("bson size (bytes): ", objectbsonC.length); +// console.log("time = ", end - start, "ms -", COUNT / ((end - start)/1000), " ops/sec"); +// console.log("MB/s = " + ((opsprsecond*objectbsonC.length)/1024)); +// +// // Benchmark 2 +// console.log(COUNT + "x (objectBSON = bsonC.serialize(object))") +// start = new Date +// +// for (j=COUNT; --j>=0; ) { +// var objectBSON = bsonC.serialize2(doc, true, false); +// } +// +// end = new Date +// var opsprsecond = COUNT / ((end - start)/1000); +// console.log("bson size (bytes): ", objectbsonC.length); +// console.log("time = ", end - start, "ms -", COUNT / ((end - start)/1000), " ops/sec"); +// console.log("MB/s = " + ((opsprsecond*objectbsonC.length)/1024)); +// +// // Benchmark 3 +// console.log(COUNT + "x (objectBSON = bsonC.serialize(object))") +// start = new Date +// +// for (j=COUNT; --j>=0; ) { +// var objectBSON = bsonC.serialize(doc, true, false); +// } +// +// end = new Date +// var opsprsecond = COUNT / ((end - start)/1000); +// console.log("bson size (bytes): ", objectbsonC.length); +// console.log("time = ", end - start, "ms -", COUNT / ((end - start)/1000), " ops/sec"); +// console.log("MB/s = " + ((opsprsecond*objectbsonC.length)/1024)); diff --git a/node_modules/mongodb/external-libs/bson/wscript b/node_modules/mongodb/external-libs/bson/wscript new file mode 100644 index 0000000..40f5317 --- /dev/null +++ b/node_modules/mongodb/external-libs/bson/wscript @@ -0,0 +1,39 @@ +import Options +from os import unlink, symlink, popen +from os.path import exists + +srcdir = "." +blddir = "build" +VERSION = "0.1.0" + +def set_options(opt): + opt.tool_options("compiler_cxx") + opt.add_option( '--debug' + , action='store_true' + , default=False + , help='Build debug variant [Default: False]' + , dest='debug' + ) + +def configure(conf): + conf.check_tool("compiler_cxx") + conf.check_tool("node_addon") + conf.env.append_value('CXXFLAGS', ['-O3', '-funroll-loops']) + + # conf.env.append_value('CXXFLAGS', ['-DDEBUG', '-g', '-O0', '-Wall', '-Wextra']) + # conf.check(lib='node', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='NODE') + +def build(bld): + obj = bld.new_task_gen("cxx", "shlib", "node_addon") + obj.target = "bson" + obj.source = ["bson.cc"] + # obj.uselib = "NODE" + +def shutdown(): + # HACK to get compress.node out of build directory. + # better way to do this? + if Options.commands['clean']: + if exists('bson.node'): unlink('bson.node') + else: + if exists('build/default/bson.node') and not exists('bson.node'): + symlink('build/default/bson.node', 'bson.node') diff --git a/node_modules/mongodb/index.js b/node_modules/mongodb/index.js new file mode 100755 index 0000000..4f59e9d --- /dev/null +++ b/node_modules/mongodb/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/mongodb'); diff --git a/node_modules/mongodb/install.js b/node_modules/mongodb/install.js new file mode 100644 index 0000000..f9f2a57 --- /dev/null +++ b/node_modules/mongodb/install.js @@ -0,0 +1,40 @@ +var spawn = require('child_process').spawn, + exec = require('child_process').exec; + +process.stdout.write("================================================================================\n"); +process.stdout.write("= =\n"); +process.stdout.write("= To install with C++ bson parser do =\n"); +process.stdout.write("= =\n"); +process.stdout.write("================================================================================\n"); + +// Check if we want to build the native code +var build_native = process.env['npm_package_config_native'] != null ? process.env['npm_package_config_native'] : 'false'; +build_native = build_native == 'true' ? true : false; +// If we are building the native bson extension ensure we use gmake if available +if(build_native) { + // Check if we need to use gmake + exec('which gmake', function(err, stdout, stderr) { + // Set up spawn command + var make = null; + // No gmake build using make + if(err != null) { + make = spawn('make', ['total']); + } else { + make = spawn('gmake', ['total']); + } + + // Execute spawn + make.stdout.on('data', function(data) { + process.stdout.write(data); + }) + + make.stderr.on('data', function(data) { + process.stdout.write(data); + }) + + make.on('exit', function(code) { + process.stdout.write('child process exited with code ' + code + "\n"); + }) + }); +} + diff --git a/node_modules/mongodb/lib/mongodb/admin.js b/node_modules/mongodb/lib/mongodb/admin.js new file mode 100644 index 0000000..cb008a3 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/admin.js @@ -0,0 +1,390 @@ +/*! + * Module dependencies. + */ +var Collection = require('./collection').Collection, + Cursor = require('./cursor').Cursor, + DbCommand = require('./commands/db_command').DbCommand; + +/** + * Allows the user to access the admin functionality of MongoDB + * + * @class Represents the Admin methods of MongoDB. + * @param {Object} db Current db instance we wish to perform Admin operations on. + * @return {Function} Constructor for Admin type. + */ +function Admin(db) { + if(!(this instanceof Admin)) return new Admin(db); + + this.db = db; +}; + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.buildInfo = function(callback) { + this.serverInfo(callback); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api private + */ +Admin.prototype.serverInfo = function(callback) { + var self = this; + var command = {buildinfo:1}; + this.command(command, function(err, doc) { + if(err != null) return callback(err, null); + return callback(null, doc.documents[0]); + }); +} + +/** + * Retrieve this db's server status. + * + * @param {Function} callback returns the server status. + * @return {null} + * @api public + */ +Admin.prototype.serverStatus = function(callback) { + var self = this; + + this.command({serverStatus: 1}, function(err, result) { + if (err == null && result.documents[0].ok == 1) { + callback(null, result.documents[0]); + } else { + if (err) { + callback(err, false); + } else { + callback(self.wrap(result.documents[0]), false); + } + } + }); +}; + +/** + * Retrieve the current profiling Level for MongoDB + * + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.profilingLevel = function(callback) { + var self = this; + var command = {profile:-1}; + + this.command(command, function(err, doc) { + doc = doc.documents[0]; + + if(err == null && (doc.ok == 1 || typeof doc.was === 'number')) { + var was = doc.was; + if(was == 0) { + callback(null, "off"); + } else if(was == 1) { + callback(null, "slow_only"); + } else if(was == 2) { + callback(null, "all"); + } else { + callback(new Error("Error: illegal profiling level value " + was), null); + } + } else { + err != null ? callback(err, null) : callback(new Error("Error with profile command"), null); + } + }); +}; + +/** + * Ping the MongoDB server and retrieve results + * + * @param {Object} [options] Optional parameters to the command. + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.ping = function(options, callback) { + // Unpack calls + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + options = args.length ? args.shift() : {}; + // Set self + var self = this; + var databaseName = this.db.databaseName; + this.db.databaseName = 'admin'; + this.db.executeDbCommand({ping:1}, options, function(err, result) { + self.db.databaseName = databaseName; + return callback(err, result); + }) +} + +/** + * Authenticate against MongoDB + * + * @param {String} username The user name for the authentication. + * @param {String} password The password for the authentication. + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.authenticate = function(username, password, callback) { + var self = this; + var databaseName = this.db.databaseName; + this.db.databaseName = 'admin'; + this.db.authenticate(username, password, function(err, result) { + self.db.databaseName = databaseName; + return callback(err, result); + }) +} + +/** + * Logout current authenticated user + * + * @param {Object} [options] Optional parameters to the command. + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.logout = function(callback) { + var self = this; + var databaseName = this.db.databaseName; + this.db.databaseName = 'admin'; + this.db.logout(function(err, result) { + return callback(err, result); + }) + + self.db.databaseName = databaseName; +} + +/** + * Add a user to the MongoDB server, if the user exists it will + * overwrite the current password + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * + * @param {String} username The user name for the authentication. + * @param {String} password The password for the authentication. + * @param {Object} [options] additional options during update. + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.addUser = function(username, password, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + var self = this; + var databaseName = this.db.databaseName; + this.db.databaseName = 'admin'; + this.db.addUser(username, password, options, function(err, result) { + self.db.databaseName = databaseName; + return callback(err, result); + }) +} + +/** + * Remove a user from the MongoDB server + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * + * @param {String} username The user name for the authentication. + * @param {Object} [options] additional options during update. + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.removeUser = function(username, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + var self = this; + var databaseName = this.db.databaseName; + this.db.databaseName = 'admin'; + this.db.removeUser(username, options, function(err, result) { + self.db.databaseName = databaseName; + return callback(err, result); + }) +} + +/** + * Set the current profiling level of MongoDB + * + * @param {String} level The new profiling level (off, slow_only, all) + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.setProfilingLevel = function(level, callback) { + var self = this; + var command = {}; + var profile = 0; + + if(level == "off") { + profile = 0; + } else if(level == "slow_only") { + profile = 1; + } else if(level == "all") { + profile = 2; + } else { + return callback(new Error("Error: illegal profiling level value " + level)); + } + + // Set up the profile number + command['profile'] = profile; + // Execute the command to set the profiling level + this.command(command, function(err, doc) { + doc = doc.documents[0]; + + if(err == null && (doc.ok == 1 || typeof doc.was === 'number')) { + return callback(null, level); + } else { + return err != null ? callback(err, null) : callback(new Error("Error with profile command"), null); + } + }); +}; + +/** + * Retrive the current profiling information for MongoDB + * + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.profilingInfo = function(callback) { + var self = this; + var databaseName = this.db.databaseName; + this.db.databaseName = 'admin'; + + try { + new Cursor(this.db, new Collection(this.db, DbCommand.SYSTEM_PROFILE_COLLECTION), {}).toArray(function(err, items) { + return callback(err, items); + }); + } catch (err) { + return callback(err, null); + } + + self.db.databaseName = databaseName; +}; + +/** + * Execute a db command against the Admin database + * + * @param {Object} command A command object `{ping:1}`. + * @param {Object} [options] Optional parameters to the command. + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.command = function(command, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + // Execute a command + this.db.executeDbAdminCommand(command, options, function(err, result) { + // Ensure change before event loop executes + return callback != null ? callback(err, result) : null; + }); +} + +/** + * Validate an existing collection + * + * @param {String} collectionName The name of the collection to validate. + * @param {Object} [options] Optional parameters to the command. + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.validateCollection = function(collectionName, options, callback) { + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + var self = this; + var command = {validate: collectionName}; + var keys = Object.keys(options); + + // Decorate command with extra options + for(var i = 0; i < keys.length; i++) { + if(options.hasOwnProperty(keys[i])) { + command[keys[i]] = options[keys[i]]; + } + } + + this.db.executeDbCommand(command, function(err, doc) { + if(err != null) return callback(err, null); + doc = doc.documents[0]; + + if(doc.ok == 0) { + return callback(new Error("Error with validate command"), null); + } else if(doc.result != null && doc.result.constructor != String) { + return callback(new Error("Error with validation data"), null); + } else if(doc.result != null && doc.result.match(/exception|corrupt/) != null) { + return callback(new Error("Error: invalid collection " + collectionName), null); + } else if(doc.valid != null && !doc.valid) { + return callback(new Error("Error: invalid collection " + collectionName), null); + } else { + return callback(null, doc); + } + }); +}; + +/** + * List the available databases + * + * @param {Function} callback Callback function of format `function(err, result) {}`. + * @return {null} Returns no result + * @api public + */ +Admin.prototype.listDatabases = function(callback) { + // Execute the listAllDatabases command + this.db.executeDbAdminCommand({listDatabases:1}, {}, function(err, result) { + if(err != null) { + callback(err, null); + } else { + callback(null, result.documents[0]); + } + }); +} + +/** + * Get ReplicaSet status + * + * @param {Function} callback returns the replica set status (if available). + * @return {null} + * @api public + */ +Admin.prototype.replSetGetStatus = function(callback) { + var self = this; + + this.command({replSetGetStatus:1}, function(err, result) { + if (err == null && result.documents[0].ok == 1) { + callback(null, result.documents[0]); + } else { + if (err) { + callback(err, false); + } else { + callback(self.db.wrap(result.documents[0]), false); + } + } + }); +}; + +/** + * @ignore + */ +exports.Admin = Admin; diff --git a/node_modules/mongodb/lib/mongodb/collection.js b/node_modules/mongodb/lib/mongodb/collection.js new file mode 100644 index 0000000..1c57c5b --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/collection.js @@ -0,0 +1,1504 @@ +/** + * Module dependencies. + * @ignore + */ +var InsertCommand = require('./commands/insert_command').InsertCommand + , QueryCommand = require('./commands/query_command').QueryCommand + , DeleteCommand = require('./commands/delete_command').DeleteCommand + , UpdateCommand = require('./commands/update_command').UpdateCommand + , DbCommand = require('./commands/db_command').DbCommand + , ObjectID = require('bson').ObjectID + , Code = require('bson').Code + , Cursor = require('./cursor').Cursor + , utils = require('./utils'); + +/** + * Precompiled regexes + * @ignore +**/ +const eErrorMessages = /No matching object found/; + +/** + * toString helper. + * @ignore + */ +var toString = Object.prototype.toString; + +/** + * Create a new Collection instance + * + * Options + * - **slaveOk** {Boolean, default:false}, Allow reads from secondaries. + * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. + * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. + * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. + * + * @class Represents a Collection + * @param {Object} db db instance. + * @param {String} collectionName collection name. + * @param {Object} [pkFactory] alternative primary key factory. + * @param {Object} [options] additional options for the collection. + * @return {Object} a collection instance. + */ +function Collection (db, collectionName, pkFactory, options) { + if(!(this instanceof Collection)) return new Collection(db, collectionName, pkFactory, options); + + checkCollectionName(collectionName); + + this.db = db; + this.collectionName = collectionName; + this.internalHint; + this.opts = options != null && ('object' === typeof options) ? options : {}; + this.slaveOk = options == null || options.slaveOk == null ? db.slaveOk : options.slaveOk; + this.serializeFunctions = options == null || options.serializeFunctions == null ? db.serializeFunctions : options.serializeFunctions; + this.raw = options == null || options.raw == null ? db.raw : options.raw; + this.pkFactory = pkFactory == null + ? ObjectID + : pkFactory; + + var self = this + Object.defineProperty(this, "hint", { + enumerable: true + , get: function () { + return this.internalHint; + } + , set: function (v) { + this.internalHint = normalizeHintField(v); + } + }); +}; + +/** + * Inserts a single document or a an array of documents into MongoDB. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * - **keepGoing** {Boolean, default:false}, keep inserting documents even if one document has an error, *mongodb 1.9.1 >*. + * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. + * + * @param {Array|Object} docs + * @param {Object} [options] optional options for insert command + * @param {Function} [callback] optional callback for the function, must be provided when using `safe` or `strict` mode + * @return {null} + * @api public + */ +Collection.prototype.insert = function insert (docs, options, callback) { + if ('function' === typeof options) callback = options, options = {}; + if(options == null) options = {}; + if(!('function' === typeof callback)) callback = null; + var self = this; + insertAll(self, Array.isArray(docs) ? docs : [docs], options, callback); + return this; +}; + +/** + * @ignore + */ +var checkCollectionName = function checkCollectionName (collectionName) { + if ('string' !== typeof collectionName) { + throw Error("collection name must be a String"); + } + + if (!collectionName || collectionName.indexOf('..') != -1) { + throw Error("collection names cannot be empty"); + } + + if (collectionName.indexOf('$') != -1 && + collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null) { + throw Error("collection names must not contain '$'"); + } + + if (collectionName.match(/^\.|\.$/) != null) { + throw Error("collection names must not start or end with '.'"); + } +}; + +/** + * Removes documents specified by `selector` from the db. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * + * @param {Object} [selector] optional select, no selector is equivalent to removing all documents. + * @param {Object} [options] additional options during remove. + * @param {Function} [callback] must be provided if you performing a safe remove + * @return {null} + * @api public + */ +Collection.prototype.remove = function remove(selector, options, callback) { + if ('function' === typeof selector) { + callback = selector; + selector = options = {}; + } else if ('function' === typeof options) { + callback = options; + options = {}; + } + + // Ensure options + if(options == null) options = {}; + if(!('function' === typeof callback)) callback = null; + // Ensure we have at least an empty selector + selector = selector == null ? {} : selector; + + var deleteCommand = new DeleteCommand( + this.db + , this.db.databaseName + "." + this.collectionName + , selector); + + var self = this; + var errorOptions = options.safe != null ? options.safe : null; + errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; + errorOptions = errorOptions == null && this.db.strict != null ? this.db.strict : errorOptions; + + // If we have a write concern set and no callback throw error + if(errorOptions && errorOptions['safe'] != false && typeof callback !== 'function') throw new Error("safe cannot be used without a callback"); + // Execute the command, do not add a callback as it's async + if (options && options.safe || this.opts.safe != null || this.db.strict) { + // Insert options + var commandOptions = {read:false}; + // If we have safe set set async to false + if(errorOptions == null) commandOptions['async'] = true; + // Set safe option + commandOptions['safe'] = true; + // If we have an error option + if(typeof errorOptions == 'object') { + var keys = Object.keys(errorOptions); + for(var i = 0; i < keys.length; i++) { + commandOptions[keys[i]] = errorOptions[keys[i]]; + } + } + + // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) + this.db._executeRemoveCommand(deleteCommand, commandOptions, function (err, error) { + error = error && error.documents; + if(!callback) return; + + if(err) { + callback(err); + } else if(error[0].err || error[0].errmsg) { + callback(self.db.wrap(error[0])); + } else { + callback(null, error[0].n); + } + }); + } else { + var result = this.db._executeRemoveCommand(deleteCommand); + // If no callback just return + if (!callback) return; + // If error return error + if (result instanceof Error) { + return callback(result); + } + // Otherwise just return + return callback(); + } +}; + +/** + * Renames the collection. + * + * @param {String} newName the new name of the collection. + * @param {Function} callback the callback accepting the result + * @return {null} + * @api public + */ +Collection.prototype.rename = function rename (newName, callback) { + var self = this; + // Ensure the new name is valid + checkCollectionName(newName); + // Execute the command, return the new renamed collection if successful + self.db._executeQueryCommand(DbCommand.createRenameCollectionCommand(self.db, self.collectionName, newName), function(err, result) { + if(err == null && result.documents[0].ok == 1) { + if(callback != null) { + // Set current object to point to the new name + self.collectionName = newName; + // Return the current collection + callback(null, self); + } + } else if(result.documents[0].errmsg != null) { + if(callback != null) { + err != null ? callback(err, null) : callback(self.db.wrap(result.documents[0]), null); + } + } + }); +}; + +/** + * @ignore + */ +var insertAll = function insertAll (self, docs, options, callback) { + if('function' === typeof options) callback = options, options = {}; + if(options == null) options = {}; + if(!('function' === typeof callback)) callback = null; + + // Insert options (flags for insert) + var insertFlags = {}; + // If we have a mongodb version >= 1.9.1 support keepGoing attribute + if(options['keepGoing'] != null) { + insertFlags['keepGoing'] = options['keepGoing']; + } + + // Either use override on the function, or go back to default on either the collection + // level or db + if(options['serializeFunctions'] != null) { + insertFlags['serializeFunctions'] = options['serializeFunctions']; + } else { + insertFlags['serializeFunctions'] = self.serializeFunctions; + } + + // Pass in options + var insertCommand = new InsertCommand( + self.db + , self.db.databaseName + "." + self.collectionName, true, insertFlags); + + // Add the documents and decorate them with id's if they have none + for (var index = 0, len = docs.length; index < len; ++index) { + var doc = docs[index]; + + // Add id to each document if it's not already defined + if (!(Buffer.isBuffer(doc)) && doc['_id'] == null && self.db.forceServerObjectId != true) { + doc['_id'] = self.pkFactory.createPk(); + } + + insertCommand.add(doc); + } + + // Collect errorOptions + var errorOptions = options.safe != null ? options.safe : null; + errorOptions = errorOptions == null && self.opts.safe != null ? self.opts.safe : errorOptions; + errorOptions = errorOptions == null && self.db.strict != null ? self.db.strict : errorOptions; + + // If we have a write concern set and no callback throw error + if(errorOptions && errorOptions['safe'] != false && typeof callback !== 'function') throw new Error("safe cannot be used without a callback"); + + // Default command options + var commandOptions = {}; + // If safe is defined check for error message + if(errorOptions && errorOptions != false) { + // Insert options + commandOptions['read'] = false; + // If we have safe set set async to false + if(errorOptions == null) commandOptions['async'] = true; + + // Set safe option + commandOptions['safe'] = errorOptions; + // If we have an error option + if(typeof errorOptions == 'object') { + var keys = Object.keys(errorOptions); + for(var i = 0; i < keys.length; i++) { + commandOptions[keys[i]] = errorOptions[keys[i]]; + } + } + + // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) + self.db._executeInsertCommand(insertCommand, commandOptions, function (err, error) { + error = error && error.documents; + if(!callback) return; + + if (err) { + callback(err); + } else if(error[0].err || error[0].errmsg) { + callback(self.db.wrap(error[0])); + } else { + callback(null, docs); + } + }); + } else { + var result = self.db._executeInsertCommand(insertCommand, commandOptions); + // If no callback just return + if(!callback) return; + // If error return error + if(result instanceof Error) { + return callback(result); + } + // Otherwise just return + return callback(null, docs); + } +}; + +/** + * Save a document. Simple full document replacement function. Not recommended for efficiency, use atomic + * operators and update instead for more efficient operations. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * + * @param {Object} [doc] the document to save + * @param {Object} [options] additional options during remove. + * @param {Function} [callback] must be provided if you performing a safe save + * @return {null} + * @api public + */ +Collection.prototype.save = function save(doc, options, callback) { + if('function' === typeof options) callback = options, options = null; + if(options == null) options = {}; + if(!('function' === typeof callback)) callback = null; + + var errorOptions = options.safe != null ? options.safe : false; + errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; + // Extract the id, if we have one we need to do a update command + var id = doc['_id']; + + if(id) { + this.update({ _id: id }, doc, { upsert: true, safe: errorOptions }, callback); + } else { + this.insert(doc, { safe: errorOptions }, callback && function (err, docs) { + if (err) return callback(err, null); + + if (Array.isArray(docs)) { + callback(err, docs[0]); + } else { + callback(err, docs); + } + }); + } +}; + +/** + * Updates documents. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * - **upsert** {Boolean, default:false}, perform an upsert operation. + * - **multi** {Boolean, default:false}, update all documents matching the selector. + * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. + * + * @param {Object} selector the query to select the document/documents to be updated + * @param {Object} document the fields/vals to be updated, or in the case of an upsert operation, inserted. + * @param {Object} [options] additional options during update. + * @param {Function} [callback] must be provided if you performing a safe update + * @return {null} + * @api public + */ +Collection.prototype.update = function update(selector, document, options, callback) { + if('function' === typeof options) callback = options, options = null; + if(options == null) options = {}; + if(!('function' === typeof callback)) callback = null; + + // Either use override on the function, or go back to default on either the collection + // level or db + if(options['serializeFunctions'] != null) { + options['serializeFunctions'] = options['serializeFunctions']; + } else { + options['serializeFunctions'] = this.serializeFunctions; + } + + var updateCommand = new UpdateCommand( + this.db + , this.db.databaseName + "." + this.collectionName + , selector + , document + , options); + + var self = this; + // Unpack the error options if any + var errorOptions = (options && options.safe != null) ? options.safe : null; + errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; + errorOptions = errorOptions == null && this.db.strict != null ? this.db.strict : errorOptions; + + // If we have a write concern set and no callback throw error + if(errorOptions && errorOptions['safe'] != false && typeof callback !== 'function') throw new Error("safe cannot be used without a callback"); + + // If we are executing in strict mode or safe both the update and the safe command must happen on the same line + if(errorOptions && errorOptions != false) { + // Insert options + var commandOptions = {read:false}; + // If we have safe set set async to false + if(errorOptions == null) commandOptions['async'] = true; + // Set safe option + commandOptions['safe'] = true; + // If we have an error option + if(typeof errorOptions == 'object') { + var keys = Object.keys(errorOptions); + for(var i = 0; i < keys.length; i++) { + commandOptions[keys[i]] = errorOptions[keys[i]]; + } + } + + // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) + this.db._executeUpdateCommand(updateCommand, commandOptions, function (err, error) { + error = error && error.documents; + if(!callback) return; + + if(err) { + callback(err); + } else if(error[0].err || error[0].errmsg) { + callback(self.db.wrap(error[0])); + } else { + callback(null, error[0].n); + } + }); + } else { + // Execute update + var result = this.db._executeUpdateCommand(updateCommand); + // If no callback just return + if (!callback) return; + // If error return error + if (result instanceof Error) { + return callback(result); + } + // Otherwise just return + return callback(); + } +}; + +/** + * The distinct command returns returns a list of distinct values for the given key across a collection. + * + * @param {String} key key to run distinct against. + * @param {Object} [query] option query to narrow the returned objects. + * @param {Function} callback must be provided. + * @return {null} + * @api public + */ +Collection.prototype.distinct = function distinct(key, query, callback) { + if ('function' === typeof query) callback = query, query = {}; + + var mapCommandHash = { + distinct: this.collectionName + , query: query + , key: key + }; + + var cmd = DbCommand.createDbSlaveOkCommand(this.db, mapCommandHash); + + this.db._executeQueryCommand(cmd, {read:true}, function (err, result) { + if (err) { + return callback(err); + } + + if (result.documents[0].ok != 1) { + return callback(new Error(result.documents[0].errmsg)); + } + + callback(null, result.documents[0].values); + }); +}; + +/** + * Count number of matching documents in the db to a query. + * + * @param {Object} [query] query to filter by before performing count. + * @param {Function} callback must be provided. + * @return {null} + * @api public + */ +Collection.prototype.count = function count (query, callback) { + if ('function' === typeof query) callback = query, query = {}; + + var final_query = { + count: this.collectionName + , query: query + , fields: null + }; + + var queryOptions = QueryCommand.OPTS_NO_CURSOR_TIMEOUT; + if (this.slaveOk || this.db.slaveOk) { + queryOptions |= QueryCommand.OPTS_SLAVE; + } + + var queryCommand = new QueryCommand( + this.db + , this.db.databaseName + ".$cmd" + , queryOptions + , 0 + , -1 + , final_query + , null + ); + + var self = this; + this.db._executeQueryCommand(queryCommand, {read:true}, function (err, result) { + result = result && result.documents; + if(!callback) return; + + if (err) { + callback(err); + } else if (result[0].ok != 1 || result[0].errmsg) { + callback(self.db.wrap(result[0])); + } else { + callback(null, result[0].n); + } + }); +}; + + +/** + * Drop the collection + * + * @param {Function} [callback] provide a callback to be notified when command finished executing + * @return {null} + * @api public + */ +Collection.prototype.drop = function drop(callback) { + this.db.dropCollection(this.collectionName, callback); +}; + +/** + * Find and update a document. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * - **remove** {Boolean, default:false}, set to true to remove the object before returning. + * - **upsert** {Boolean, default:false}, perform an upsert operation. + * - **new** {Boolean, default:false}, set to true if you want to return the modified object rather than the original. Ignored for remove. + * + * @param {Object} query query object to locate the object to modify + * @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate + * @param {Object} doc - the fields/vals to be updated + * @param {Object} [options] additional options during update. + * @param {Function} [callback] returns results. + * @return {null} + * @api public + */ +Collection.prototype.findAndModify = function findAndModify (query, sort, doc, options, callback) { + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + sort = args.length ? args.shift() : []; + doc = args.length ? args.shift() : null; + options = args.length ? args.shift() : {}; + var self = this; + + var queryObject = { + 'findandmodify': this.collectionName + , 'query': query + , 'sort': utils.formattedOrderClause(sort) + }; + + queryObject.new = options.new ? 1 : 0; + queryObject.remove = options.remove ? 1 : 0; + queryObject.upsert = options.upsert ? 1 : 0; + + if (options.fields) { + queryObject.fields = options.fields; + } + + if (doc && !options.remove) { + queryObject.update = doc; + } + + // Either use override on the function, or go back to default on either the collection + // level or db + if(options['serializeFunctions'] != null) { + options['serializeFunctions'] = options['serializeFunctions']; + } else { + options['serializeFunctions'] = this.serializeFunctions; + } + + // Unpack the error options if any + var errorOptions = (options && options.safe != null) ? options.safe : null; + errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; + errorOptions = errorOptions == null && this.db.strict != null ? this.db.strict : errorOptions; + + // Commands to send + var commands = []; + // Add the find and modify command + commands.push(DbCommand.createDbSlaveOkCommand(this.db, queryObject, options)); + // If we have safe defined we need to return both call results + var chainedCommands = errorOptions != null ? true : false; + // Add error command if we have one + if(chainedCommands) { + commands.push(DbCommand.createGetLastErrorCommand(errorOptions, this.db)); + } + + // Fire commands and + this.db._executeQueryCommand(commands, function(err, result) { + result = result && result.documents; + + if(err != null) { + callback(err); + } else if(result[0].err != null) { + callback(self.db.wrap(result[0]), null); + } else if(result[0].errmsg != null && !result[0].errmsg.match(eErrorMessages)) { + // Workaround due to 1.8.X returning an error on no matching object + // while 2.0.X does not not, making 2.0.X behaviour standard + callback(self.db.wrap(result[0]), null); + } else { + return callback(null, result[0].value); + } + }); +} + +/** + * Find and remove a document + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * + * @param {Object} query query object to locate the object to modify + * @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate + * @param {Object} [options] additional options during update. + * @param {Function} [callback] returns results. + * @return {null} + * @api public + */ +Collection.prototype.findAndRemove = function(query, sort, options, callback) { + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + sort = args.length ? args.shift() : []; + options = args.length ? args.shift() : {}; + // Add the remove option + options['remove'] = true; + // Execute the callback + this.findAndModify(query, sort, null, options, callback); +} + +var testForFields = {'limit' : 1, 'sort' : 1, 'fields' : 1, 'skip' : 1, 'hint' : 1, 'explain' : 1, 'snapshot' : 1 + , 'timeout' : 1, 'tailable' : 1, 'batchSize' : 1, 'raw' : 1, 'read' : 1 + , 'returnKey' : 1, 'maxScan' : 1, 'min' : 1, 'max' : 1, 'showDiskLoc' : 1, 'comment' : 1}; + +/** + * Creates a cursor for a query that can be used to iterate over results from MongoDB + * + * Various argument possibilities + * - callback? + * - selector, callback?, + * - selector, fields, callback? + * - selector, options, callback? + * - selector, fields, options, callback? + * - selector, fields, skip, limit, callback? + * - selector, fields, skip, limit, timeout, callback? + * + * Options + * - **limit** {Number, default:0}, sets the limit of documents returned in the query. + * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. + * - **fields** {Object}, the fields to return in the query. Object of fields to include or exclude (not both), {'a':1} + * - **skip** {Number, default:0}, set to skip N documents ahead in your query (useful for pagination). + * - **hint** {Object}, tell the query to use specific indexes in the query. Object of indexes to use, {'_id':1} + * - **explain** {Boolean, default:false}, explain the query instead of returning the data. + * - **snapshot** {Boolean, default:false}, snapshot query. + * - **timeout** {Boolean, default:false}, specify if the cursor can timeout. + * - **tailable** {Boolean, default:false}, specify if the cursor is tailable. + * - **batchSize** {Number, default:0}, set the batchSize for the getMoreCommand when iterating over the query results. + * - **returnKey** {Boolean, default:false}, only return the index key. + * - **maxScan** {Number}, Limit the number of items to scan. + * - **min** {Number}, Set index bounds. + * - **max** {Number}, Set index bounds. + * - **showDiskLoc** {Boolean, default:false}, Show disk location of results. + * - **comment** {String}, You can put a $comment field on a query to make looking in the profiler logs simpler. + * - **raw** {Boolean, default:false}, Return all BSON documents as Raw Buffer documents. + * - **read** {Boolean, default:false}, Tell the query to read from a secondary server. + * + * @param {Object} query query object to locate the object to modify + * @param {Object} [options] additional options during update. + * @param {Function} [callback] optional callback for cursor. + * @return {Cursor} returns a cursor to the query + * @api public + */ +Collection.prototype.find = function find () { + var options + , args = Array.prototype.slice.call(arguments, 0) + , has_callback = typeof args[args.length - 1] === 'function' + , has_weird_callback = typeof args[0] === 'function' + , callback = has_callback ? args.pop() : (has_weird_callback ? args.shift() : null) + , len = args.length + , selector = len >= 1 ? args[0] : {} + , fields = len >= 2 ? args[1] : undefined; + + if(len === 1 && has_weird_callback) { + // backwards compat for callback?, options case + selector = {}; + options = args[0]; + } + + if(len === 2 && !Array.isArray(fields)) { + var fieldKeys = Object.getOwnPropertyNames(fields); + var is_option = false; + + for(var i = 0; i < fieldKeys.length; i++) { + if(testForFields[fieldKeys[i]] != null) { + is_option = true; + break; + } + } + + if(is_option) { + options = fields; + fields = undefined; + } else { + options = {}; + } + } else if(len === 2 && Array.isArray(fields) && !Array.isArray(fields[0])) { + var newFields = {}; + // Rewrite the array + for(var i = 0; i < fields.length; i++) { + newFields[fields[i]] = 1; + } + // Set the fields + fields = newFields; + } + + if(3 === len) { + options = args[2]; + } + + // Ensure selector is not null + selector = selector == null ? {} : selector; + // Validate correctness off the selector + var object = selector; + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) { + var error = new Error("query selector raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + error.name = 'MongoError'; + throw error; + } + } + + // Validate correctness of the field selector + var object = fields; + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) { + var error = new Error("query fields raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + error.name = 'MongoError'; + throw error; + } + } + + // Check special case where we are using an objectId + if(selector instanceof ObjectID) { + selector = {_id:selector}; + } + + // If it's a serialized fields field we need to just let it through + // user be warned it better be good + if(options && options.fields && !(Buffer.isBuffer(options.fields))) { + fields = {}; + + if(Array.isArray(options.fields)) { + if(!options.fields.length) { + fields['_id'] = 1; + } else { + for (var i = 0, l = options.fields.length; i < l; i++) { + fields[options.fields[i]] = 1; + } + } + } else { + fields = options.fields; + } + } + + if (!options) options = {}; + options.skip = len > 3 ? args[2] : options.skip ? options.skip : 0; + options.limit = len > 3 ? args[3] : options.limit ? options.limit : 0; + options.raw = options.raw != null && typeof options.raw === 'boolean' ? options.raw : this.raw; + options.hint = options.hint != null ? normalizeHintField(options.hint) : this.internalHint; + options.timeout = len == 5 ? args[4] : typeof options.timeout === 'undefined' ? undefined : options.timeout; + // If we have overridden slaveOk otherwise use the default db setting + options.slaveOk = options.slaveOk != null ? options.slaveOk : this.db.slaveOk; + var o = options; + + // callback for backward compatibility + if(callback) { + // TODO refactor Cursor args + callback(null, new Cursor(this.db, this, selector, fields, o.skip, o.limit + , o.sort, o.hint, o.explain, o.snapshot, o.timeout, o.tailable, o.batchSize + , o.slaveOk, o.raw, o.read, o.returnKey, o.maxScan, o.min, o.max, o.showDiskLoc, o.comment)); + } else { + return new Cursor(this.db, this, selector, fields, o.skip, o.limit + , o.sort, o.hint, o.explain, o.snapshot, o.timeout, o.tailable, o.batchSize + , o.slaveOk, o.raw, o.read, o.returnKey, o.maxScan, o.min, o.max, o.showDiskLoc, o.comment); + } +}; + +/** + * Normalizes a `hint` argument. + * + * @param {String|Object|Array} hint + * @return {Object} + * @api private + */ +var normalizeHintField = function normalizeHintField(hint) { + var finalHint = null; + + if (null != hint) { + switch (hint.constructor) { + case String: + finalHint = {}; + finalHint[hint] = 1; + break; + case Object: + finalHint = {}; + for (var name in hint) { + finalHint[name] = hint[name]; + } + break; + case Array: + finalHint = {}; + hint.forEach(function(param) { + finalHint[param] = 1; + }); + break; + } + } + + return finalHint; +}; + +/** + * Finds a single document based on the query + * + * Various argument possibilities + * - callback? + * - selector, callback?, + * - selector, fields, callback? + * - selector, options, callback? + * - selector, fields, options, callback? + * - selector, fields, skip, limit, callback? + * - selector, fields, skip, limit, timeout, callback? + * + * Options + * - **limit** {Number, default:0}, sets the limit of documents returned in the query. + * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. + * - **fields** {Object}, the fields to return in the query. Object of fields to include or exclude (not both), {'a':1} + * - **skip** {Number, default:0}, set to skip N documents ahead in your query (useful for pagination). + * - **hint** {Object}, tell the query to use specific indexes in the query. Object of indexes to use, {'_id':1} + * - **explain** {Boolean, default:false}, explain the query instead of returning the data. + * - **snapshot** {Boolean, default:false}, snapshot query. + * - **timeout** {Boolean, default:false}, specify if the cursor can timeout. + * - **tailable** {Boolean, default:false}, specify if the cursor is tailable. + * - **batchSize** {Number, default:0}, set the batchSize for the getMoreCommand when iterating over the query results. + * - **returnKey** {Boolean, default:false}, only return the index key. + * - **maxScan** {Number}, Limit the number of items to scan. + * - **min** {Number}, Set index bounds. + * - **max** {Number}, Set index bounds. + * - **showDiskLoc** {Boolean, default:false}, Show disk location of results. + * - **comment** {String}, You can put a $comment field on a query to make looking in the profiler logs simpler. + * - **raw** {Boolean, default:false}, Return all BSON documents as Raw Buffer documents. + * - **read** {Boolean, default:false}, Tell the query to read from a secondary server. + * + * @param {Object} query query object to locate the object to modify + * @param {Object} [options] additional options during update. + * @param {Function} [callback] optional callback for cursor. + * @return {Cursor} returns a cursor to the query + * @api public + */ +Collection.prototype.findOne = function findOne () { + var self = this; + var args = Array.prototype.slice.call(arguments, 0); + var callback = args.pop(); + var cursor = this.find.apply(this, args).limit(1).batchSize(1); + // Return the item + cursor.toArray(function(err, items) { + if(err != null) return callback(err instanceof Error ? err : self.db.wrap(new Error(err)), null); + if(items.length == 1) return callback(null, items[0]); + callback(null, null); + }); +}; + +/** + * Creates an index on the collection. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a + * - **unique** {Boolean, default:false}, creates an unique index. + * - **sparse** {Boolean, default:false}, creates a sparse index. + * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. + * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value + * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. + * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. + * + * @param {Object} fieldOrSpec fieldOrSpec that defines the index. + * @param {Object} [options] additional options during update. + * @param {Function} callback for results. + * @return {null} + * @api public + */ +Collection.prototype.createIndex = function createIndex (fieldOrSpec, options, callback) { + // Clean up call + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + options = typeof callback === 'function' ? options : callback; + options = options == null ? {} : options; + + // Collect errorOptions + var errorOptions = options.safe != null ? options.safe : null; + errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; + errorOptions = errorOptions == null && this.db.strict != null ? this.db.strict : errorOptions; + + // If we have a write concern set and no callback throw error + if(errorOptions != null && errorOptions != false && (typeof callback !== 'function' && typeof options !== 'function')) throw new Error("safe cannot be used without a callback"); + + // Execute create index + this.db.createIndex(this.collectionName, fieldOrSpec, options, callback); +}; + +/** + * Ensures that an index exists, if it does not it creates it + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a + * - **unique** {Boolean, default:false}, creates an unique index. + * - **sparse** {Boolean, default:false}, creates a sparse index. + * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. + * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value + * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. + * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. + * - **v** {Number}, specify the format version of the indexes. + * + * @param {Object} fieldOrSpec fieldOrSpec that defines the index. + * @param {Object} [options] additional options during update. + * @param {Function} callback for results. + * @return {null} + * @api public + */ +Collection.prototype.ensureIndex = function ensureIndex (fieldOrSpec, options, callback) { + // Clean up call + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + options = typeof callback === 'function' ? options : callback; + options = options == null ? {} : options; + + // Collect errorOptions + var errorOptions = options.safe != null ? options.safe : null; + errorOptions = errorOptions == null && this.opts.safe != null ? this.opts.safe : errorOptions; + errorOptions = errorOptions == null && this.db.strict != null ? this.db.strict : errorOptions; + + // If we have a write concern set and no callback throw error + if(errorOptions != null && errorOptions != false && (typeof callback !== 'function' && typeof options !== 'function')) throw new Error("safe cannot be used without a callback"); + + // Execute create index + this.db.ensureIndex(this.collectionName, fieldOrSpec, options, callback); +}; + +/** + * Retrieves this collections index info. + * + * Options + * - **full** {Boolean, default:false}, returns the full raw index information. + * + * @param {Object} [options] additional options during update. + * @param {Function} callback returns the index information. + * @return {null} + * @api public + */ +Collection.prototype.indexInformation = function indexInformation (options, callback) { + // Unpack calls + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + options = args.length ? args.shift() : {}; + // Call the index information + this.db.indexInformation(this.collectionName, options, callback); +}; + +/** + * Drops an index from this collection. + * + * @param {String} name + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Collection.prototype.dropIndex = function dropIndex (name, callback) { + this.db.dropIndex(this.collectionName, name, callback); +}; + +/** + * Drops all indexes from this collection. + * + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Collection.prototype.dropAllIndexes = function dropIndexes (callback) { + this.db.dropIndex(this.collectionName, '*', function (err, result) { + if(err != null) { + callback(err, false); + } else if(result.documents[0].errmsg == null) { + callback(null, true); + } else { + callback(new Error(result.documents[0].errmsg), false); + } + }); +}; + +/** + * Drops all indexes from this collection. + * + * @deprecated + * @param {Function} callback returns the results. + * @return {null} + * @api private + */ +Collection.prototype.dropIndexes = Collection.prototype.dropAllIndexes; + +/** + * Reindex all indexes on the collection + * Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections. + * + * @param {Function} callback returns the results. + * @return {null} + * @api public +**/ +Collection.prototype.reIndex = function(callback) { + this.db.reIndex(this.collectionName, callback); +} + +/** + * Run Map Reduce across a collection. + * + * Options + * - **out** {Object, default:*{inline:1}*}, sets the output target for the map reduce job. *{inline:1} | {replace:'collectionName'} | {merge:'collectionName'} | {reduce:'collectionName'}* + * - **query** {Object}, query filter object. + * - **sort** {Object}, sorts the input objects using this key. Useful for optimization, like sorting by the emit key for fewer reduces. + * - **limit** {Number}, number of objects to return from collection. + * - **keeptemp** {Boolean, default:false}, keep temporary data. + * - **finalize** {Function | String}, finalize function. + * - **scope** {Object}, can pass in variables that can be access from map/reduce/finalize. + * - **jsMode** {Boolean, default:false}, it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X. + * - **verbose** {Boolean, default:false}, provide statistics on job execution time. + * + * @param {Function|String} map the mapping function. + * @param {Function|String} reduce the reduce function. + * @param {Objects} [options] options for the map reduce job. + * @param {Function} callback returns the result of the map reduce job, (error, results, [stats]) + * @return {null} + * @api public + */ +Collection.prototype.mapReduce = function mapReduce (map, reduce, options, callback) { + if ('function' === typeof options) callback = options, options = {}; + // Out must allways be defined (make sure we don't break weirdly on pre 1.8+ servers) + if(null == options.out) { + throw new Error("the out option parameter must be defined, see mongodb docs for possible values"); + } + + if ('function' === typeof map) { + map = map.toString(); + } + + if ('function' === typeof reduce) { + reduce = reduce.toString(); + } + + if ('function' === typeof options.finalize) { + options.finalize = options.finalize.toString(); + } + + var mapCommandHash = { + mapreduce: this.collectionName + , map: map + , reduce: reduce + }; + + // Add any other options passed in + for (var name in options) { + mapCommandHash[name] = options[name]; + } + + var self = this; + var cmd = DbCommand.createDbSlaveOkCommand(this.db, mapCommandHash); + + this.db._executeQueryCommand(cmd, {read:true}, function (err, result) { + if (err) { + return callback(err); + } + + // + if (1 != result.documents[0].ok || result.documents[0].err || result.documents[0].errmsg) { + return callback(self.db.wrap(result.documents[0])); + } + + // Create statistics value + var stats = {}; + if(result.documents[0].timeMillis) stats['processtime'] = result.documents[0].timeMillis; + if(result.documents[0].counts) stats['counts'] = result.documents[0].counts; + if(result.documents[0].timing) stats['timing'] = result.documents[0].timing; + + // invoked with inline? + if (result.documents[0].results) { + return callback(null, result.documents[0].results, stats); + } + + // Create a collection object that wraps the result collection + self.db.collection(result.documents[0].result, function (err, collection) { + // If we wish for no verbosity + if(options['verbose'] == null || !options['verbose']) { + return callback(err, collection); + } + + // Create statistics value + var stats = {}; + if(result.documents[0].timeMillis) stats['processtime'] = result.documents[0].timeMillis; + if(result.documents[0].counts) stats['counts'] = result.documents[0].counts; + if(result.documents[0].timing) stats['timing'] = result.documents[0].timing; + // Return stats as third set of values + callback(err, collection, stats); + }); + }); +}; + +/** + * Group function helper + * @ignore + */ +var groupFunction = function () { + var c = db[ns].find(condition); + var map = new Map(); + var reduce_function = reduce; + + while (c.hasNext()) { + var obj = c.next(); + var key = {}; + + for (var i = 0, len = keys.length; i < len; ++i) { + var k = keys[i]; + key[k] = obj[k]; + } + + var aggObj = map.get(key); + + if (aggObj == null) { + var newObj = Object.extend({}, key); + aggObj = Object.extend(newObj, initial); + map.put(key, aggObj); + } + + reduce_function(obj, aggObj); + } + + return { "result": map.values() }; +}.toString(); + +/** + * Run a group command across a collection + * + * @param {Object|Array|Function|Code} keys an object, array or function expressing the keys to group by. + * @param {Object} condition an optional condition that must be true for a row to be considered. + * @param {Object} initial initial value of the aggregation counter object. + * @param {Function|Code} reduce the reduce function aggregates (reduces) the objects iterated + * @param {Function|Code} finalize an optional function to be run on each item in the result set just before the item is returned. + * @param {Boolean} command specify if you wish to run using the internal group command or using eval, default is true. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Collection.prototype.group = function group(keys, condition, initial, reduce, finalize, command, callback) { + var args = Array.prototype.slice.call(arguments, 3); + callback = args.pop(); + // Fetch all commands + reduce = args.length ? args.shift() : null; + finalize = args.length ? args.shift() : null; + command = args.length ? args.shift() : null; + + // Make sure we are backward compatible + if(!(typeof finalize == 'function')) { + command = finalize; + finalize = null; + } + + if (!Array.isArray(keys) && keys instanceof Object && typeof(keys) !== 'function') { + keys = Object.keys(keys); + } + + if(typeof reduce === 'function') { + reduce = reduce.toString(); + } + + if(typeof finalize === 'function') { + finalize = finalize.toString(); + } + + // Set up the command as default + command = command == null ? true : command; + + // Execute using the command + if(command) { + var reduceFunction = reduce instanceof Code + ? reduce + : new Code(reduce); + + var selector = { + group: { + 'ns': this.collectionName + , '$reduce': reduceFunction + , 'cond': condition + , 'initial': initial + , 'out': "inline" + } + }; + + // if finalize is defined + if(finalize != null) selector.group['finalize'] = finalize; + // Set up group selector + if ('function' === typeof keys) { + selector.group.$keyf = keys instanceof Code + ? keys + : new Code(keys); + } else { + var hash = {}; + keys.forEach(function (key) { + hash[key] = 1; + }); + selector.group.key = hash; + } + + var cmd = DbCommand.createDbSlaveOkCommand(this.db, selector); + + this.db._executeQueryCommand(cmd, {read:true}, function (err, result) { + if(err != null) return callback(err); + + var document = result.documents[0]; + if (null == document.retval) { + return callback(new Error("group command failed: " + document.errmsg)); + } + + callback(null, document.retval); + }); + + } else { + // Create execution scope + var scope = reduce != null && reduce instanceof Code + ? reduce.scope + : {}; + + scope.ns = this.collectionName; + scope.keys = keys; + scope.condition = condition; + scope.initial = initial; + + // Pass in the function text to execute within mongodb. + var groupfn = groupFunction.replace(/ reduce;/, reduce.toString() + ';'); + + this.db.eval(new Code(groupfn, scope), function (err, results) { + if (err) return callback(err, null); + callback(null, results.result || results); + }); + } +}; + +/** + * Returns the options of the collection. + * + * @param {Function} callback returns option results. + * @return {null} + * @api public + */ +Collection.prototype.options = function options(callback) { + this.db.collectionsInfo(this.collectionName, function (err, cursor) { + if (err) return callback(err); + cursor.nextObject(function (err, document) { + callback(err, document && document.options || null); + }); + }); +}; + +/** + * Returns if the collection is a capped collection + * + * @param {Function} callback returns if collection is capped. + * @return {null} + * @api public + */ +Collection.prototype.isCapped = function isCapped(callback) { + this.options(function(err, document) { + if(err != null) { + callback(err); + } else { + callback(null, document.capped); + } + }); +}; + +/** + * Checks if one or more indexes exist on the collection + * + * @param {String|Array} indexNames check if one or more indexes exist on the collection. + * @param {Function} callback returns if the indexes exist. + * @return {null} + * @api public + */ +Collection.prototype.indexExists = function indexExists(indexes, callback) { + this.indexInformation(function(err, indexInformation) { + // If we have an error return + if(err != null) return callback(err, null); + // Let's check for the index names + if(Array.isArray(indexes)) { + for(var i = 0; i < indexes.length; i++) { + if(indexInformation[indexes[i]] == null) { + return callback(null, false); + } + } + + // All keys found return true + return callback(null, true); + } else { + return callback(null, indexInformation[indexes] != null); + } + }); +} + +/** + * Execute the geoNear command to search for items in the collection + * + * Options + * - **num** {Number}, max number of results to return. + * - **maxDistance** {Number}, include results up to maxDistance from the point. + * - **distanceMultiplier** {Number}, include a value to multiply the distances with allowing for range conversions. + * - **query** {Object}, filter the results by a query. + * - **spherical** {Boolean, default:false}, perform query using a spherical model. + * - **uniqueDocs** {Boolean, default:false}, the closest location in a document to the center of the search region will always be returned MongoDB > 2.X. + * - **includeLocs** {Boolean, default:false}, include the location data fields in the top level of the results MongoDB > 2.X. + * + * @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order. + * @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order. + * @param {Objects} [options] options for the map reduce job. + * @param {Function} callback returns matching documents. + * @return {null} + * @api public + */ +Collection.prototype.geoNear = function geoNear(x, y, options, callback) { + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + // Fetch all commands + options = args.length ? args.shift() : {}; + + // Build command object + var commandObject = { + geoNear:this.collectionName, + near: [x, y] + } + + // Decorate object if any with known properties + if(options['num'] != null) commandObject['num'] = options['num']; + if(options['maxDistance'] != null) commandObject['maxDistance'] = options['maxDistance']; + if(options['distanceMultiplier'] != null) commandObject['distanceMultiplier'] = options['distanceMultiplier']; + if(options['query'] != null) commandObject['query'] = options['query']; + if(options['spherical'] != null) commandObject['spherical'] = options['spherical']; + if(options['uniqueDocs'] != null) commandObject['uniqueDocs'] = options['uniqueDocs']; + if(options['includeLocs'] != null) commandObject['includeLocs'] = options['includeLocs']; + + // Execute the command + this.db.command(commandObject, callback); +} + +/** + * Execute a geo search using a geo haystack index on a collection. + * + * Options + * - **maxDistance** {Number}, include results up to maxDistance from the point. + * - **search** {Object}, filter the results by a query. + * - **limit** {Number}, max number of results to return. + * + * @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order. + * @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order. + * @param {Objects} [options] options for the map reduce job. + * @param {Function} callback returns matching documents. + * @return {null} + * @api public + */ +Collection.prototype.geoHaystackSearch = function geoHaystackSearch(x, y, options, callback) { + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + // Fetch all commands + options = args.length ? args.shift() : {}; + + // Build command object + var commandObject = { + geoSearch:this.collectionName, + near: [x, y] + } + + // Decorate object if any with known properties + if(options['maxDistance'] != null) commandObject['maxDistance'] = options['maxDistance']; + if(options['query'] != null) commandObject['search'] = options['query']; + if(options['search'] != null) commandObject['search'] = options['search']; + if(options['limit'] != null) commandObject['limit'] = options['limit']; + + // Execute the command + this.db.command(commandObject, callback); +} + +/** + * Retrieve all the indexes on the collection. + * + * @param {Function} callback returns index information. + * @return {null} + * @api public + */ +Collection.prototype.indexes = function indexes(callback) { + // Return all the index information + this.db.indexInformation(this.collectionName, {full:true}, callback); +} + +/** + * Execute an aggregation framework pipeline against the collection, needs MongoDB >= 2.1 + * + * @param {Array|Objects} pipline a pipleline containing all the object for the execution. + * @param {Function} callback returns matching documents. + * @return {null} + * @api public + */ +Collection.prototype.aggregate = function(pipeline, callback) { + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + var self = this; + + // Check if we have more than one argument then just make the pipeline + // the remaining arguments + if(args.length > 1) { + pipeline = args; + } + + // Build the command + var command = { aggregate : this.collectionName, pipeline : pipeline}; + // Execute the command + this.db.command(command, function(err, result) { + if(err) { + callback(err); + } else if(result['err'] || result['errmsg']) { + callback(self.db.wrap(result)); + } else { + callback(null, result.result); + } + }); +} + +/** + * Get all the collection statistics. + * + * Options + * - **scale** {Number}, divide the returned sizes by scale value. + * + * @param {Objects} [options] options for the map reduce job. + * @param {Function} callback returns statistical information for the collection. + * @return {null} + * @api public + */ +Collection.prototype.stats = function stats(options, callback) { + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + // Fetch all commands + options = args.length ? args.shift() : {}; + + // Build command object + var commandObject = { + collStats:this.collectionName, + } + + // Check if we have the scale value + if(options['scale'] != null) commandObject['scale'] = options['scale']; + + // Execute the command + this.db.command(commandObject, callback); +} + +/** + * Expose. + */ +exports.Collection = Collection; + + + + + + + + + + + + + diff --git a/node_modules/mongodb/lib/mongodb/commands/base_command.js b/node_modules/mongodb/lib/mongodb/commands/base_command.js new file mode 100644 index 0000000..6e531d3 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/commands/base_command.js @@ -0,0 +1,27 @@ +/** + Base object used for common functionality +**/ +var BaseCommand = exports.BaseCommand = function() { +}; + +var id = 1; +BaseCommand.prototype.getRequestId = function() { + if (!this.requestId) this.requestId = id++; + return this.requestId; +}; + +BaseCommand.prototype.updateRequestId = function() { + this.requestId = id++; + return this.requestId; +}; + +// OpCodes +BaseCommand.OP_REPLY = 1; +BaseCommand.OP_MSG = 1000; +BaseCommand.OP_UPDATE = 2001; +BaseCommand.OP_INSERT = 2002; +BaseCommand.OP_GET_BY_OID = 2003; +BaseCommand.OP_QUERY = 2004; +BaseCommand.OP_GET_MORE = 2005; +BaseCommand.OP_DELETE = 2006; +BaseCommand.OP_KILL_CURSORS = 2007; \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/commands/db_command.js b/node_modules/mongodb/lib/mongodb/commands/db_command.js new file mode 100644 index 0000000..7586886 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/commands/db_command.js @@ -0,0 +1,205 @@ +var QueryCommand = require('./query_command').QueryCommand, + InsertCommand = require('./insert_command').InsertCommand, + inherits = require('util').inherits, + crypto = require('crypto'); + +/** + Db Command +**/ +var DbCommand = exports.DbCommand = function(dbInstance, collectionName, queryOptions, numberToSkip, numberToReturn, query, returnFieldSelector, options) { + QueryCommand.call(this); + this.collectionName = collectionName; + this.queryOptions = queryOptions; + this.numberToSkip = numberToSkip; + this.numberToReturn = numberToReturn; + this.query = query; + this.returnFieldSelector = returnFieldSelector; + this.db = dbInstance; + + // Make sure we don't get a null exception + options = options == null ? {} : options; + // Let us defined on a command basis if we want functions to be serialized or not + if(options['serializeFunctions'] != null && options['serializeFunctions']) { + this.serializeFunctions = true; + } +}; + +inherits(DbCommand, QueryCommand); + +// Constants +DbCommand.SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"; +DbCommand.SYSTEM_INDEX_COLLECTION = "system.indexes"; +DbCommand.SYSTEM_PROFILE_COLLECTION = "system.profile"; +DbCommand.SYSTEM_USER_COLLECTION = "system.users"; +DbCommand.SYSTEM_COMMAND_COLLECTION = "$cmd"; + +// New commands +DbCommand.NcreateIsMasterCommand = function(db, databaseName) { + return new DbCommand(db, databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'ismaster':1}, null); +}; + +// Provide constructors for different db commands +DbCommand.createIsMasterCommand = function(db) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'ismaster':1}, null); +}; + +DbCommand.createCollectionInfoCommand = function(db, selector) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_NAMESPACE_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, 0, selector, null); +}; + +DbCommand.createGetNonceCommand = function(db) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getnonce':1}, null); +}; + +DbCommand.createAuthenticationCommand = function(db, username, password, nonce) { + // Use node md5 generator + var md5 = crypto.createHash('md5'); + // Generate keys used for authentication + md5.update(username + ":mongo:" + password); + var hash_password = md5.digest('hex'); + // Final key + md5 = crypto.createHash('md5'); + md5.update(nonce + username + hash_password); + var key = md5.digest('hex'); + // Creat selector + var selector = {'authenticate':1, 'user':username, 'nonce':nonce, 'key':key}; + // Create db command + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NONE, 0, -1, selector, null); +}; + +DbCommand.createLogoutCommand = function(db) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'logout':1}, null); +}; + +DbCommand.createCreateCollectionCommand = function(db, collectionName, options) { + var selector = {'create':collectionName}; + // Modify the options to ensure correct behaviour + for(var name in options) { + if(options[name] != null && options[name].constructor != Function) selector[name] = options[name]; + } + // Execute the command + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, selector, null); +}; + +DbCommand.createDropCollectionCommand = function(db, collectionName) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'drop':collectionName}, null); +}; + +DbCommand.createRenameCollectionCommand = function(db, fromCollectionName, toCollectionName) { + var renameCollection = db.databaseName + "." + fromCollectionName; + var toCollection = db.databaseName + "." + toCollectionName; + return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'renameCollection':renameCollection, 'to':toCollection}, null); +}; + +DbCommand.createGetLastErrorCommand = function(options, db) { + var args = Array.prototype.slice.call(arguments, 0); + db = args.pop(); + options = args.length ? args.shift() : {}; + // Final command + var command = {'getlasterror':1}; + // If we have an options Object let's merge in the fields (fsync/wtimeout/w) + if('object' === typeof options) { + for(var name in options) { + command[name] = options[name] + } + } + + // Execute command + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command, null); +}; + +DbCommand.createGetLastStatusCommand = DbCommand.createGetLastErrorCommand; + +DbCommand.createGetPreviousErrorsCommand = function(db) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getpreverror':1}, null); +}; + +DbCommand.createResetErrorHistoryCommand = function(db) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'reseterror':1}, null); +}; + +DbCommand.createCreateIndexCommand = function(db, collectionName, fieldOrSpec, options) { + var fieldHash = {}; + var indexes = []; + var keys; + + // Get all the fields accordingly + if (fieldOrSpec.constructor === String) { // 'type' + indexes.push(fieldOrSpec + '_' + 1); + fieldHash[fieldOrSpec] = 1; + } else if (fieldOrSpec.constructor === Array) { // [{location:'2d'}, ...] + fieldOrSpec.forEach(function(f) { + if (f.constructor === String) { // [{location:'2d'}, 'type'] + indexes.push(f + '_' + 1); + fieldHash[f] = 1; + } else if (f.constructor === Array) { // [['location', '2d'],['type', 1]] + indexes.push(f[0] + '_' + (f[1] || 1)); + fieldHash[f[0]] = f[1] || 1; + } else if (f.constructor === Object) { // [{location:'2d'}, {type:1}] + keys = Object.keys(f); + keys.forEach(function(k) { + indexes.push(k + '_' + f[k]); + fieldHash[k] = f[k]; + }); + } else { + // undefined + } + }); + } else if (fieldOrSpec.constructor === Object) { // {location:'2d', type:1} + keys = Object.keys(fieldOrSpec); + keys.forEach(function(key) { + indexes.push(key + '_' + fieldOrSpec[key]); + fieldHash[key] = fieldOrSpec[key]; + }); + } + + // Generate the index name + var indexName = indexes.join("_"); + // Build the selector + var selector = {'ns':(db.databaseName + "." + collectionName), 'key':fieldHash, 'name':indexName}; + + // Ensure we have a correct finalUnique + var finalUnique = options == null || 'object' === typeof options ? false : options; + // Set up options + options = options == null || typeof options == 'boolean' ? {} : options; + + // Add all the options + var keys = Object.keys(options); + // Add all the fields to the selector + for(var i = 0; i < keys.length; i++) { + selector[keys[i]] = options[keys[i]]; + } + + // If we don't have the unique property set on the selector + if(selector['unique'] == null) selector['unique'] = finalUnique; + // Create the insert command for the index and return the document + return new InsertCommand(db, db.databaseName + "." + DbCommand.SYSTEM_INDEX_COLLECTION, false).add(selector); +}; + +DbCommand.logoutCommand = function(db, command_hash) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null); +} + +DbCommand.createDropIndexCommand = function(db, collectionName, indexName) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'deleteIndexes':collectionName, 'index':indexName}, null); +}; + +DbCommand.createReIndexCommand = function(db, collectionName) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'reIndex':collectionName}, null); +}; + +DbCommand.createDropDatabaseCommand = function(db) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'dropDatabase':1}, null); +}; + +DbCommand.createDbCommand = function(db, command_hash, options) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null, options); +}; + +DbCommand.createAdminDbCommand = function(db, command_hash) { + return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null); +}; + +DbCommand.createDbSlaveOkCommand = function(db, command_hash, options) { + return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | QueryCommand.OPTS_SLAVE, 0, -1, command_hash, null, options); +}; \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/commands/delete_command.js b/node_modules/mongodb/lib/mongodb/commands/delete_command.js new file mode 100644 index 0000000..1adad07 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/commands/delete_command.js @@ -0,0 +1,111 @@ +var BaseCommand = require('./base_command').BaseCommand, + inherits = require('util').inherits; + +/** + Insert Document Command +**/ +var DeleteCommand = exports.DeleteCommand = function(db, collectionName, selector) { + BaseCommand.call(this); + + // Validate correctness off the selector + var object = selector; + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) { + var error = new Error("delete raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + error.name = 'MongoError'; + throw error; + } + } + + this.collectionName = collectionName; + this.selector = selector; + this.db = db; +}; + +inherits(DeleteCommand, BaseCommand); + +DeleteCommand.OP_DELETE = 2006; + +/* +struct { + MsgHeader header; // standard message header + int32 ZERO; // 0 - reserved for future use + cstring fullCollectionName; // "dbname.collectionname" + int32 ZERO; // 0 - reserved for future use + mongo.BSON selector; // query object. See below for details. +} +*/ +DeleteCommand.prototype.toBinary = function() { + // Calculate total length of the document + var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + this.db.bson.calculateObjectSize(this.selector, false, true) + (4 * 4); + // Let's build the single pass buffer command + var _index = 0; + var _command = new Buffer(totalLengthOfCommand); + // Write the header information to the buffer + _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; + _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; + _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; + _command[_index] = totalLengthOfCommand & 0xff; + // Adjust index + _index = _index + 4; + // Write the request ID + _command[_index + 3] = (this.requestId >> 24) & 0xff; + _command[_index + 2] = (this.requestId >> 16) & 0xff; + _command[_index + 1] = (this.requestId >> 8) & 0xff; + _command[_index] = this.requestId & 0xff; + // Adjust index + _index = _index + 4; + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + // Write the op_code for the command + _command[_index + 3] = (DeleteCommand.OP_DELETE >> 24) & 0xff; + _command[_index + 2] = (DeleteCommand.OP_DELETE >> 16) & 0xff; + _command[_index + 1] = (DeleteCommand.OP_DELETE >> 8) & 0xff; + _command[_index] = DeleteCommand.OP_DELETE & 0xff; + // Adjust index + _index = _index + 4; + + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + + // Write the collection name to the command + _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; + _command[_index - 1] = 0; + + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + + // Document binary length + var documentLength = 0 + + // Serialize the selector + // If we are passing a raw buffer, do minimal validation + if(Buffer.isBuffer(this.selector)) { + documentLength = this.selector.length; + // Copy the data into the current buffer + this.selector.copy(_command, _index); + } else { + documentLength = this.db.bson.serializeWithBufferAndIndex(this.selector, this.checkKeys, _command, _index) - _index + 1; + } + + // Write the length to the document + _command[_index + 3] = (documentLength >> 24) & 0xff; + _command[_index + 2] = (documentLength >> 16) & 0xff; + _command[_index + 1] = (documentLength >> 8) & 0xff; + _command[_index] = documentLength & 0xff; + // Update index in buffer + _index = _index + documentLength; + // Add terminating 0 for the object + _command[_index - 1] = 0; + return _command; +}; \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/commands/get_more_command.js b/node_modules/mongodb/lib/mongodb/commands/get_more_command.js new file mode 100644 index 0000000..d3aac02 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/commands/get_more_command.js @@ -0,0 +1,83 @@ +var BaseCommand = require('./base_command').BaseCommand, + inherits = require('util').inherits, + binaryutils = require('../utils'); + +/** + Get More Document Command +**/ +var GetMoreCommand = exports.GetMoreCommand = function(db, collectionName, numberToReturn, cursorId) { + BaseCommand.call(this); + + this.collectionName = collectionName; + this.numberToReturn = numberToReturn; + this.cursorId = cursorId; + this.db = db; +}; + +inherits(GetMoreCommand, BaseCommand); + +GetMoreCommand.OP_GET_MORE = 2005; + +GetMoreCommand.prototype.toBinary = function() { + // Calculate total length of the document + var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 8 + (4 * 4); + // Let's build the single pass buffer command + var _index = 0; + var _command = new Buffer(totalLengthOfCommand); + // Write the header information to the buffer + _command[_index++] = totalLengthOfCommand & 0xff; + _command[_index++] = (totalLengthOfCommand >> 8) & 0xff; + _command[_index++] = (totalLengthOfCommand >> 16) & 0xff; + _command[_index++] = (totalLengthOfCommand >> 24) & 0xff; + + // Write the request ID + _command[_index++] = this.requestId & 0xff; + _command[_index++] = (this.requestId >> 8) & 0xff; + _command[_index++] = (this.requestId >> 16) & 0xff; + _command[_index++] = (this.requestId >> 24) & 0xff; + + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + + // Write the op_code for the command + _command[_index++] = GetMoreCommand.OP_GET_MORE & 0xff; + _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 8) & 0xff; + _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 16) & 0xff; + _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 24) & 0xff; + + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + + // Write the collection name to the command + _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; + _command[_index - 1] = 0; + + // Number of documents to return + _command[_index++] = this.numberToReturn & 0xff; + _command[_index++] = (this.numberToReturn >> 8) & 0xff; + _command[_index++] = (this.numberToReturn >> 16) & 0xff; + _command[_index++] = (this.numberToReturn >> 24) & 0xff; + + // Encode the cursor id + var low_bits = this.cursorId.getLowBits(); + // Encode low bits + _command[_index++] = low_bits & 0xff; + _command[_index++] = (low_bits >> 8) & 0xff; + _command[_index++] = (low_bits >> 16) & 0xff; + _command[_index++] = (low_bits >> 24) & 0xff; + + var high_bits = this.cursorId.getHighBits(); + // Encode high bits + _command[_index++] = high_bits & 0xff; + _command[_index++] = (high_bits >> 8) & 0xff; + _command[_index++] = (high_bits >> 16) & 0xff; + _command[_index++] = (high_bits >> 24) & 0xff; + // Return command + return _command; +}; \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/commands/insert_command.js b/node_modules/mongodb/lib/mongodb/commands/insert_command.js new file mode 100644 index 0000000..3036a02 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/commands/insert_command.js @@ -0,0 +1,141 @@ +var BaseCommand = require('./base_command').BaseCommand, + inherits = require('util').inherits; + +/** + Insert Document Command +**/ +var InsertCommand = exports.InsertCommand = function(db, collectionName, checkKeys, options) { + BaseCommand.call(this); + + this.collectionName = collectionName; + this.documents = []; + this.checkKeys = checkKeys == null ? true : checkKeys; + this.db = db; + this.flags = 0; + this.serializeFunctions = false; + + // Ensure valid options hash + options = options == null ? {} : options; + + // Check if we have keepGoing set -> set flag if it's the case + if(options['keepGoing'] != null && options['keepGoing']) { + // This will finish inserting all non-index violating documents even if it returns an error + this.flags = 1; + } + + // Let us defined on a command basis if we want functions to be serialized or not + if(options['serializeFunctions'] != null && options['serializeFunctions']) { + this.serializeFunctions = true; + } +}; + +inherits(InsertCommand, BaseCommand); + +// OpCodes +InsertCommand.OP_INSERT = 2002; + +InsertCommand.prototype.add = function(document) { + if(Buffer.isBuffer(document)) { + var object_size = document[0] | document[1] << 8 | document[2] << 16 | document[3] << 24; + if(object_size != document.length) { + var error = new Error("insert raw message size does not match message header size [" + document.length + "] != [" + object_size + "]"); + error.name = 'MongoError'; + throw error; + } + } + + this.documents.push(document); + return this; +}; + +/* +struct { + MsgHeader header; // standard message header + int32 ZERO; // 0 - reserved for future use + cstring fullCollectionName; // "dbname.collectionname" + BSON[] documents; // one or more documents to insert into the collection +} +*/ +InsertCommand.prototype.toBinary = function() { + // Calculate total length of the document + var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + (4 * 4); + // var docLength = 0 + for(var i = 0; i < this.documents.length; i++) { + if(Buffer.isBuffer(this.documents[i])) { + totalLengthOfCommand += this.documents[i].length; + } else { + // Calculate size of document + totalLengthOfCommand += this.db.bson.calculateObjectSize(this.documents[i], this.serializeFunctions, true); + } + } + + // Let's build the single pass buffer command + var _index = 0; + var _command = new Buffer(totalLengthOfCommand); + // Write the header information to the buffer + _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; + _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; + _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; + _command[_index] = totalLengthOfCommand & 0xff; + // Adjust index + _index = _index + 4; + // Write the request ID + _command[_index + 3] = (this.requestId >> 24) & 0xff; + _command[_index + 2] = (this.requestId >> 16) & 0xff; + _command[_index + 1] = (this.requestId >> 8) & 0xff; + _command[_index] = this.requestId & 0xff; + // Adjust index + _index = _index + 4; + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + // Write the op_code for the command + _command[_index + 3] = (InsertCommand.OP_INSERT >> 24) & 0xff; + _command[_index + 2] = (InsertCommand.OP_INSERT >> 16) & 0xff; + _command[_index + 1] = (InsertCommand.OP_INSERT >> 8) & 0xff; + _command[_index] = InsertCommand.OP_INSERT & 0xff; + // Adjust index + _index = _index + 4; + // Write flags if any + _command[_index + 3] = (this.flags >> 24) & 0xff; + _command[_index + 2] = (this.flags >> 16) & 0xff; + _command[_index + 1] = (this.flags >> 8) & 0xff; + _command[_index] = this.flags & 0xff; + // Adjust index + _index = _index + 4; + // Write the collection name to the command + _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; + _command[_index - 1] = 0; + + // Write all the bson documents to the buffer at the index offset + for(var i = 0; i < this.documents.length; i++) { + // Document binary length + var documentLength = 0 + var object = this.documents[i]; + + // Serialize the selector + // If we are passing a raw buffer, do minimal validation + if(Buffer.isBuffer(object)) { + documentLength = object.length; + // Copy the data into the current buffer + object.copy(_command, _index); + } else { + // Serialize the document straight to the buffer + documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; + } + + // Write the length to the document + _command[_index + 3] = (documentLength >> 24) & 0xff; + _command[_index + 2] = (documentLength >> 16) & 0xff; + _command[_index + 1] = (documentLength >> 8) & 0xff; + _command[_index] = documentLength & 0xff; + // Update index in buffer + _index = _index + documentLength; + // Add terminating 0 for the object + _command[_index - 1] = 0; + } + + return _command; +}; diff --git a/node_modules/mongodb/lib/mongodb/commands/kill_cursor_command.js b/node_modules/mongodb/lib/mongodb/commands/kill_cursor_command.js new file mode 100644 index 0000000..d8ccb0c --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/commands/kill_cursor_command.js @@ -0,0 +1,98 @@ +var BaseCommand = require('./base_command').BaseCommand, + inherits = require('util').inherits, + binaryutils = require('../utils'); + +/** + Insert Document Command +**/ +var KillCursorCommand = exports.KillCursorCommand = function(db, cursorIds) { + BaseCommand.call(this); + + this.cursorIds = cursorIds; + this.db = db; +}; + +inherits(KillCursorCommand, BaseCommand); + +KillCursorCommand.OP_KILL_CURSORS = 2007; + +/* +struct { + MsgHeader header; // standard message header + int32 ZERO; // 0 - reserved for future use + int32 numberOfCursorIDs; // number of cursorIDs in message + int64[] cursorIDs; // array of cursorIDs to close +} +*/ +KillCursorCommand.prototype.toBinary = function() { + // Calculate total length of the document + var totalLengthOfCommand = 4 + 4 + (4 * 4) + (this.cursorIds.length * 8); + // Let's build the single pass buffer command + var _index = 0; + var _command = new Buffer(totalLengthOfCommand); + // Write the header information to the buffer + _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; + _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; + _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; + _command[_index] = totalLengthOfCommand & 0xff; + // Adjust index + _index = _index + 4; + // Write the request ID + _command[_index + 3] = (this.requestId >> 24) & 0xff; + _command[_index + 2] = (this.requestId >> 16) & 0xff; + _command[_index + 1] = (this.requestId >> 8) & 0xff; + _command[_index] = this.requestId & 0xff; + // Adjust index + _index = _index + 4; + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + // Write the op_code for the command + _command[_index + 3] = (KillCursorCommand.OP_KILL_CURSORS >> 24) & 0xff; + _command[_index + 2] = (KillCursorCommand.OP_KILL_CURSORS >> 16) & 0xff; + _command[_index + 1] = (KillCursorCommand.OP_KILL_CURSORS >> 8) & 0xff; + _command[_index] = KillCursorCommand.OP_KILL_CURSORS & 0xff; + // Adjust index + _index = _index + 4; + + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + + // Number of cursors to kill + var numberOfCursors = this.cursorIds.length; + _command[_index + 3] = (numberOfCursors >> 24) & 0xff; + _command[_index + 2] = (numberOfCursors >> 16) & 0xff; + _command[_index + 1] = (numberOfCursors >> 8) & 0xff; + _command[_index] = numberOfCursors & 0xff; + // Adjust index + _index = _index + 4; + + // Encode all the cursors + for(var i = 0; i < this.cursorIds.length; i++) { + // Encode the cursor id + var low_bits = this.cursorIds[i].getLowBits(); + // Encode low bits + _command[_index + 3] = (low_bits >> 24) & 0xff; + _command[_index + 2] = (low_bits >> 16) & 0xff; + _command[_index + 1] = (low_bits >> 8) & 0xff; + _command[_index] = low_bits & 0xff; + // Adjust index + _index = _index + 4; + + var high_bits = this.cursorIds[i].getHighBits(); + // Encode high bits + _command[_index + 3] = (high_bits >> 24) & 0xff; + _command[_index + 2] = (high_bits >> 16) & 0xff; + _command[_index + 1] = (high_bits >> 8) & 0xff; + _command[_index] = high_bits & 0xff; + // Adjust index + _index = _index + 4; + } + + return _command; +}; \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/commands/query_command.js b/node_modules/mongodb/lib/mongodb/commands/query_command.js new file mode 100644 index 0000000..e07a8aa --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/commands/query_command.js @@ -0,0 +1,209 @@ +var BaseCommand = require('./base_command').BaseCommand, + inherits = require('util').inherits; + +/** + Insert Document Command +**/ +var QueryCommand = exports.QueryCommand = function(db, collectionName, queryOptions, numberToSkip, numberToReturn, query, returnFieldSelector, options) { + BaseCommand.call(this); + + // Validate correctness off the selector + var object = query; + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) { + var error = new Error("query selector raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + error.name = 'MongoError'; + throw error; + } + } + + var object = returnFieldSelector; + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) { + var error = new Error("query fields raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + error.name = 'MongoError'; + throw error; + } + } + + // Make sure we don't get a null exception + options = options == null ? {} : options; + // Set up options + this.collectionName = collectionName; + this.queryOptions = queryOptions; + this.numberToSkip = numberToSkip; + this.numberToReturn = numberToReturn; + this.query = query; + this.returnFieldSelector = returnFieldSelector; + this.db = db; + + // Let us defined on a command basis if we want functions to be serialized or not + if(options['serializeFunctions'] != null && options['serializeFunctions']) { + this.serializeFunctions = true; + } +}; + +inherits(QueryCommand, BaseCommand); + +QueryCommand.OP_QUERY = 2004; + +/* +struct { + MsgHeader header; // standard message header + int32 opts; // query options. See below for details. + cstring fullCollectionName; // "dbname.collectionname" + int32 numberToSkip; // number of documents to skip when returning results + int32 numberToReturn; // number of documents to return in the first OP_REPLY + BSON query ; // query object. See below for details. + [ BSON returnFieldSelector; ] // OPTIONAL : selector indicating the fields to return. See below for details. +} +*/ +QueryCommand.prototype.toBinary = function() { + var totalLengthOfCommand = 0; + // Calculate total length of the document + if(Buffer.isBuffer(this.query)) { + totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 4 + this.query.length + (4 * 4); + } else { + totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 4 + this.db.bson.calculateObjectSize(this.query, this.serializeFunctions, true) + (4 * 4); + } + + // Calculate extra fields size + if(this.returnFieldSelector != null && !(Buffer.isBuffer(this.returnFieldSelector))) { + if(Object.keys(this.returnFieldSelector).length > 0) { + totalLengthOfCommand += this.db.bson.calculateObjectSize(this.returnFieldSelector, this.serializeFunctions, true); + } + } else if(Buffer.isBuffer(this.returnFieldSelector)) { + totalLengthOfCommand += this.returnFieldSelector.length; + } + + // Let's build the single pass buffer command + var _index = 0; + var _command = new Buffer(totalLengthOfCommand); + // Write the header information to the buffer + _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; + _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; + _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; + _command[_index] = totalLengthOfCommand & 0xff; + // Adjust index + _index = _index + 4; + // Write the request ID + _command[_index + 3] = (this.requestId >> 24) & 0xff; + _command[_index + 2] = (this.requestId >> 16) & 0xff; + _command[_index + 1] = (this.requestId >> 8) & 0xff; + _command[_index] = this.requestId & 0xff; + // Adjust index + _index = _index + 4; + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + // Write the op_code for the command + _command[_index + 3] = (QueryCommand.OP_QUERY >> 24) & 0xff; + _command[_index + 2] = (QueryCommand.OP_QUERY >> 16) & 0xff; + _command[_index + 1] = (QueryCommand.OP_QUERY >> 8) & 0xff; + _command[_index] = QueryCommand.OP_QUERY & 0xff; + // Adjust index + _index = _index + 4; + + // Write the query options + _command[_index + 3] = (this.queryOptions >> 24) & 0xff; + _command[_index + 2] = (this.queryOptions >> 16) & 0xff; + _command[_index + 1] = (this.queryOptions >> 8) & 0xff; + _command[_index] = this.queryOptions & 0xff; + // Adjust index + _index = _index + 4; + + // Write the collection name to the command + _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; + _command[_index - 1] = 0; + + // Write the number of documents to skip + _command[_index + 3] = (this.numberToSkip >> 24) & 0xff; + _command[_index + 2] = (this.numberToSkip >> 16) & 0xff; + _command[_index + 1] = (this.numberToSkip >> 8) & 0xff; + _command[_index] = this.numberToSkip & 0xff; + // Adjust index + _index = _index + 4; + + // Write the number of documents to return + _command[_index + 3] = (this.numberToReturn >> 24) & 0xff; + _command[_index + 2] = (this.numberToReturn >> 16) & 0xff; + _command[_index + 1] = (this.numberToReturn >> 8) & 0xff; + _command[_index] = this.numberToReturn & 0xff; + // Adjust index + _index = _index + 4; + + // Document binary length + var documentLength = 0 + var object = this.query; + + // Serialize the selector + if(Buffer.isBuffer(object)) { + documentLength = object.length; + // Copy the data into the current buffer + object.copy(_command, _index); + } else { + // Serialize the document straight to the buffer + documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; + } + + // Write the length to the document + _command[_index + 3] = (documentLength >> 24) & 0xff; + _command[_index + 2] = (documentLength >> 16) & 0xff; + _command[_index + 1] = (documentLength >> 8) & 0xff; + _command[_index] = documentLength & 0xff; + // Update index in buffer + _index = _index + documentLength; + // Add terminating 0 for the object + _command[_index - 1] = 0; + + // Push field selector if available + if(this.returnFieldSelector != null && !(Buffer.isBuffer(this.returnFieldSelector))) { + if(Object.keys(this.returnFieldSelector).length > 0) { + var documentLength = this.db.bson.serializeWithBufferAndIndex(this.returnFieldSelector, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; + // Write the length to the document + _command[_index + 3] = (documentLength >> 24) & 0xff; + _command[_index + 2] = (documentLength >> 16) & 0xff; + _command[_index + 1] = (documentLength >> 8) & 0xff; + _command[_index] = documentLength & 0xff; + // Update index in buffer + _index = _index + documentLength; + // Add terminating 0 for the object + _command[_index - 1] = 0; + } + } if(this.returnFieldSelector != null && Buffer.isBuffer(this.returnFieldSelector)) { + // Document binary length + var documentLength = 0 + var object = this.returnFieldSelector; + + // Serialize the selector + documentLength = object.length; + // Copy the data into the current buffer + object.copy(_command, _index); + + // Write the length to the document + _command[_index + 3] = (documentLength >> 24) & 0xff; + _command[_index + 2] = (documentLength >> 16) & 0xff; + _command[_index + 1] = (documentLength >> 8) & 0xff; + _command[_index] = documentLength & 0xff; + // Update index in buffer + _index = _index + documentLength; + // Add terminating 0 for the object + _command[_index - 1] = 0; + } + + // Return finished command + return _command; +}; + +// Constants +QueryCommand.OPTS_NONE = 0; +QueryCommand.OPTS_TAILABLE_CURSOR = 2; +QueryCommand.OPTS_SLAVE = 4; +QueryCommand.OPTS_OPLOG_REPLY = 8; +QueryCommand.OPTS_NO_CURSOR_TIMEOUT = 16; +QueryCommand.OPTS_AWAIT_DATA = 32; +QueryCommand.OPTS_EXHAUST = 64; \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/commands/update_command.js b/node_modules/mongodb/lib/mongodb/commands/update_command.js new file mode 100644 index 0000000..9829dea --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/commands/update_command.js @@ -0,0 +1,174 @@ +var BaseCommand = require('./base_command').BaseCommand, + inherits = require('util').inherits; + +/** + Update Document Command +**/ +var UpdateCommand = exports.UpdateCommand = function(db, collectionName, spec, document, options) { + BaseCommand.call(this); + + var object = spec; + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) { + var error = new Error("update spec raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + error.name = 'MongoError'; + throw error; + } + } + + var object = document; + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) { + var error = new Error("update document raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + error.name = 'MongoError'; + throw error; + } + } + + this.collectionName = collectionName; + this.spec = spec; + this.document = document; + this.db = db; + this.serializeFunctions = false; + + // Generate correct flags + var db_upsert = 0; + var db_multi_update = 0; + db_upsert = options != null && options['upsert'] != null ? (options['upsert'] == true ? 1 : 0) : db_upsert; + db_multi_update = options != null && options['multi'] != null ? (options['multi'] == true ? 1 : 0) : db_multi_update; + + // Flags + this.flags = parseInt(db_multi_update.toString() + db_upsert.toString(), 2); + // Let us defined on a command basis if we want functions to be serialized or not + if(options['serializeFunctions'] != null && options['serializeFunctions']) { + this.serializeFunctions = true; + } +}; + +inherits(UpdateCommand, BaseCommand); + +UpdateCommand.OP_UPDATE = 2001; + +/* +struct { + MsgHeader header; // standard message header + int32 ZERO; // 0 - reserved for future use + cstring fullCollectionName; // "dbname.collectionname" + int32 flags; // bit vector. see below + BSON spec; // the query to select the document + BSON document; // the document data to update with or insert +} +*/ +UpdateCommand.prototype.toBinary = function() { + // Calculate total length of the document + var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + this.db.bson.calculateObjectSize(this.spec, false, true) + + this.db.bson.calculateObjectSize(this.document, this.serializeFunctions, true) + (4 * 4); + + // Let's build the single pass buffer command + var _index = 0; + var _command = new Buffer(totalLengthOfCommand); + // Write the header information to the buffer + _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; + _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; + _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; + _command[_index] = totalLengthOfCommand & 0xff; + // Adjust index + _index = _index + 4; + // Write the request ID + _command[_index + 3] = (this.requestId >> 24) & 0xff; + _command[_index + 2] = (this.requestId >> 16) & 0xff; + _command[_index + 1] = (this.requestId >> 8) & 0xff; + _command[_index] = this.requestId & 0xff; + // Adjust index + _index = _index + 4; + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + // Write the op_code for the command + _command[_index + 3] = (UpdateCommand.OP_UPDATE >> 24) & 0xff; + _command[_index + 2] = (UpdateCommand.OP_UPDATE >> 16) & 0xff; + _command[_index + 1] = (UpdateCommand.OP_UPDATE >> 8) & 0xff; + _command[_index] = UpdateCommand.OP_UPDATE & 0xff; + // Adjust index + _index = _index + 4; + + // Write zero + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + _command[_index++] = 0; + + // Write the collection name to the command + _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; + _command[_index - 1] = 0; + + // Write the update flags + _command[_index + 3] = (this.flags >> 24) & 0xff; + _command[_index + 2] = (this.flags >> 16) & 0xff; + _command[_index + 1] = (this.flags >> 8) & 0xff; + _command[_index] = this.flags & 0xff; + // Adjust index + _index = _index + 4; + + // Document binary length + var documentLength = 0 + var object = this.spec; + + // Serialize the selector + // If we are passing a raw buffer, do minimal validation + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) throw new Error("raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + documentLength = object.length; + // Copy the data into the current buffer + object.copy(_command, _index); + } else { + documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, false) - _index + 1; + } + + // Write the length to the document + _command[_index + 3] = (documentLength >> 24) & 0xff; + _command[_index + 2] = (documentLength >> 16) & 0xff; + _command[_index + 1] = (documentLength >> 8) & 0xff; + _command[_index] = documentLength & 0xff; + // Update index in buffer + _index = _index + documentLength; + // Add terminating 0 for the object + _command[_index - 1] = 0; + + // Document binary length + var documentLength = 0 + var object = this.document; + + // Serialize the document + // If we are passing a raw buffer, do minimal validation + if(Buffer.isBuffer(object)) { + var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; + if(object_size != object.length) throw new Error("raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); + documentLength = object.length; + // Copy the data into the current buffer + object.copy(_command, _index); + } else { + documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; + } + + // Write the length to the document + _command[_index + 3] = (documentLength >> 24) & 0xff; + _command[_index + 2] = (documentLength >> 16) & 0xff; + _command[_index + 1] = (documentLength >> 8) & 0xff; + _command[_index] = documentLength & 0xff; + // Update index in buffer + _index = _index + documentLength; + // Add terminating 0 for the object + _command[_index - 1] = 0; + + return _command; +}; + +// Constants +UpdateCommand.DB_UPSERT = 0; +UpdateCommand.DB_MULTI_UPDATE = 1; \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/connection/connection.js b/node_modules/mongodb/lib/mongodb/connection/connection.js new file mode 100644 index 0000000..86818bb --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/connection/connection.js @@ -0,0 +1,414 @@ +var utils = require('./connection_utils'), + inherits = require('util').inherits, + net = require('net'), + EventEmitter = require('events').EventEmitter, + inherits = require('util').inherits, + binaryutils = require('../utils'), + tls = require('tls'); + +var Connection = exports.Connection = function(id, socketOptions) { + // Set up event emitter + EventEmitter.call(this); + // Store all socket options + this.socketOptions = socketOptions ? socketOptions : {host:'localhost', port:27017}; + // Id for the connection + this.id = id; + // State of the connection + this.connected = false; + + // + // Connection parsing state + // + this.maxBsonSize = socketOptions.maxBsonSize ? socketOptions.maxBsonSize : Connection.DEFAULT_MAX_BSON_SIZE; + // Contains the current message bytes + this.buffer = null; + // Contains the current message size + this.sizeOfMessage = 0; + // Contains the readIndex for the messaage + this.bytesRead = 0; + // Contains spill over bytes from additional messages + this.stubBuffer = 0; + + // Just keeps list of events we allow + this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[], end:[]}; + + // Just keeps list of events we allow + resetHandlers(this, false); +} + +// Set max bson size +Connection.DEFAULT_MAX_BSON_SIZE = 4 * 1024 * 1024 * 4 * 3; + +// Inherit event emitter so we can emit stuff wohoo +inherits(Connection, EventEmitter); + +Connection.prototype.start = function() { + // If we have a normal connection + if(this.socketOptions.ssl) { + // Create a new stream + this.connection = new net.Socket(); + // Set options on the socket + this.connection.setTimeout(this.socketOptions.timeout); + // Work around for 0.4.X + if(process.version.indexOf("v0.4") == -1) this.connection.setNoDelay(this.socketOptions.noDelay); + // Set keep alive if defined + if(process.version.indexOf("v0.4") == -1) { + if(this.socketOptions.keepAlive > 0) { + this.connection.setKeepAlive(true, this.socketOptions.keepAlive); + } else { + this.connection.setKeepAlive(false); + } + } + + // Set up pair for tls with server, accept self-signed certificates as well + var pair = this.pair = tls.createSecurePair(false); + // Set up encrypted streams + this.pair.encrypted.pipe(this.connection); + this.connection.pipe(this.pair.encrypted); + + // Setup clearText stream + this.writeSteam = this.pair.cleartext; + // Add all handlers to the socket to manage it + this.pair.on("secure", connectHandler(this)); + this.pair.cleartext.on("data", createDataHandler(this)); + // Add handlers + this.connection.on("error", errorHandler(this)); + // this.connection.on("connect", connectHandler(this)); + this.connection.on("end", endHandler(this)); + this.connection.on("timeout", timeoutHandler(this)); + this.connection.on("drain", drainHandler(this)); + this.writeSteam.on("close", closeHandler(this)); + // Start socket + this.connection.connect(this.socketOptions.port, this.socketOptions.host); + } else { + // // Create a new stream + // this.connection = new net.Stream(); + // // Create new connection instance + // this.connection = new net.Socket(); + this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host); + // Set options on the socket + this.connection.setTimeout(this.socketOptions.timeout); + // Work around for 0.4.X + if(process.version.indexOf("v0.4") == -1) this.connection.setNoDelay(this.socketOptions.noDelay); + // Set keep alive if defined + if(process.version.indexOf("v0.4") == -1) { + if(this.socketOptions.keepAlive > 0) { + this.connection.setKeepAlive(true, this.socketOptions.keepAlive); + } else { + this.connection.setKeepAlive(false); + } + } + + // Set up write stream + this.writeSteam = this.connection; + // Add handlers + this.connection.on("error", errorHandler(this)); + // Add all handlers to the socket to manage it + this.connection.on("connect", connectHandler(this)); + // this.connection.on("end", endHandler(this)); + this.connection.on("data", createDataHandler(this)); + this.connection.on("timeout", timeoutHandler(this)); + this.connection.on("drain", drainHandler(this)); + this.connection.on("close", closeHandler(this)); + // // Start socket + // this.connection.connect(this.socketOptions.port, this.socketOptions.host); + } +} + +// Check if the sockets are live +Connection.prototype.isConnected = function() { + return this.connected && !this.connection.destroyed && this.connection.writable && this.connection.readable; +} + +// Write the data out to the socket +Connection.prototype.write = function(command, callback) { + try { + // If we have a list off commands to be executed on the same socket + if(Array.isArray(command)) { + for(var i = 0; i < command.length; i++) { + var binaryCommand = command[i].toBinary() + if(this.logger != null && this.logger.doDebug) this.logger.debug("writing command to mongodb", binaryCommand); + var r = this.writeSteam.write(binaryCommand); + } + } else { + var binaryCommand = command.toBinary() + if(this.logger != null && this.logger.doDebug) this.logger.debug("writing command to mongodb", binaryCommand); + var r = this.writeSteam.write(binaryCommand); + } + } catch (err) { + if(typeof callback === 'function') callback(err); + } +} + +// Force the closure of the connection +Connection.prototype.close = function() { + // clear out all the listeners + resetHandlers(this, true); + // Add a dummy error listener to catch any weird last moment errors (and ignore them) + this.connection.on("error", function() {}) + // destroy connection + this.connection.destroy(); +} + +// Reset all handlers +var resetHandlers = function(self, clearListeners) { + self.eventHandlers = {error:[], connect:[], close:[], end:[], timeout:[], parseError:[], message:[]}; + + // If we want to clear all the listeners + if(clearListeners && self.connection != null) { + var keys = Object.keys(self.eventHandlers); + // Remove all listeners + for(var i = 0; i < keys.length; i++) { + self.connection.removeAllListeners(keys[i]); + } + } +} + +// +// Handlers +// + +// Connect handler +var connectHandler = function(self) { + return function() { + // Set connected + self.connected = true; + // Emit the connect event with no error + self.emit("connect", null, self); + } +} + +var createDataHandler = exports.Connection.createDataHandler = function(self) { + // We need to handle the parsing of the data + // and emit the messages when there is a complete one + return function(data) { + // Parse until we are done with the data + while(data.length > 0) { + // If we still have bytes to read on the current message + if(self.bytesRead > 0 && self.sizeOfMessage > 0) { + // Calculate the amount of remaining bytes + var remainingBytesToRead = self.sizeOfMessage - self.bytesRead; + // Check if the current chunk contains the rest of the message + if(remainingBytesToRead > data.length) { + // Copy the new data into the exiting buffer (should have been allocated when we know the message size) + data.copy(self.buffer, self.bytesRead); + // Adjust the number of bytes read so it point to the correct index in the buffer + self.bytesRead = self.bytesRead + data.length; + + // Reset state of buffer + data = new Buffer(0); + } else { + // Copy the missing part of the data into our current buffer + data.copy(self.buffer, self.bytesRead, 0, remainingBytesToRead); + // Slice the overflow into a new buffer that we will then re-parse + data = data.slice(remainingBytesToRead); + + // Emit current complete message + try { + self.emit("message", self.buffer, self); + + } catch(err) { + var errorObject = {err:"socketHandler", trace:err, bin:buffer, parseState:{ + sizeOfMessage:self.sizeOfMessage, + bytesRead:self.bytesRead, + stubBuffer:self.stubBuffer}}; + if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); + // We got a parse Error fire it off then keep going + self.emit("parseError", errorObject, self); + } + + // Reset state of buffer + self.buffer = null; + self.sizeOfMessage = 0; + self.bytesRead = 0; + self.stubBuffer = null; + } + } else { + // Stub buffer is kept in case we don't get enough bytes to determine the + // size of the message (< 4 bytes) + if(self.stubBuffer != null && self.stubBuffer.length > 0) { + + // If we have enough bytes to determine the message size let's do it + if(self.stubBuffer.length + data.length > 4) { + // Prepad the data + var newData = new Buffer(self.stubBuffer.length + data.length); + self.stubBuffer.copy(newData, 0); + data.copy(newData, self.stubBuffer.length); + // Reassign for parsing + data = newData; + + // Reset state of buffer + self.buffer = null; + self.sizeOfMessage = 0; + self.bytesRead = 0; + self.stubBuffer = null; + + } else { + + // Add the the bytes to the stub buffer + var newStubBuffer = new Buffer(self.stubBuffer.length + data.length); + // Copy existing stub buffer + self.stubBuffer.copy(newStubBuffer, 0); + // Copy missing part of the data + data.copy(newStubBuffer, self.stubBuffer.length); + // Exit parsing loop + data = new Buffer(0); + } + } else { + if(data.length > 4) { + // Retrieve the message size + var sizeOfMessage = binaryutils.decodeUInt32(data, 0); + // If we have a negative sizeOfMessage emit error and return + if(sizeOfMessage < 0 || sizeOfMessage > self.maxBsonSize) { + var errorObject = {err:"socketHandler", trace:'', bin:self.buffer, parseState:{ + sizeOfMessage:sizeOfMessage, + bytesRead:self.bytesRead, + stubBuffer:self.stubBuffer}}; + if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); + // We got a parse Error fire it off then keep going + self.emit("parseError", errorObject, self); + return; + } + + // Ensure that the size of message is larger than 0 and less than the max allowed + if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonSize && sizeOfMessage > data.length) { + self.buffer = new Buffer(sizeOfMessage); + // Copy all the data into the buffer + data.copy(self.buffer, 0); + // Update bytes read + self.bytesRead = data.length; + // Update sizeOfMessage + self.sizeOfMessage = sizeOfMessage; + // Ensure stub buffer is null + self.stubBuffer = null; + // Exit parsing loop + data = new Buffer(0); + + } else if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonSize && sizeOfMessage == data.length) { + try { + self.emit("message", data, self); + // Reset state of buffer + self.buffer = null; + self.sizeOfMessage = 0; + self.bytesRead = 0; + self.stubBuffer = null; + // Exit parsing loop + data = new Buffer(0); + + } catch (err) { + var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{ + sizeOfMessage:self.sizeOfMessage, + bytesRead:self.bytesRead, + stubBuffer:self.stubBuffer}}; + if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); + // We got a parse Error fire it off then keep going + self.emit("parseError", errorObject, self); + } + } else if(sizeOfMessage <= 4 || sizeOfMessage > self.maxBsonSize) { + var errorObject = {err:"socketHandler", trace:null, bin:data, parseState:{ + sizeOfMessage:sizeOfMessage, + bytesRead:0, + buffer:null, + stubBuffer:null}}; + if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); + // We got a parse Error fire it off then keep going + self.emit("parseError", errorObject, self); + + // Clear out the state of the parser + self.buffer = null; + self.sizeOfMessage = 0; + self.bytesRead = 0; + self.stubBuffer = null; + // Exit parsing loop + data = new Buffer(0); + + } else { + try { + self.emit("message", data.slice(0, sizeOfMessage), self); + // Reset state of buffer + self.buffer = null; + self.sizeOfMessage = 0; + self.bytesRead = 0; + self.stubBuffer = null; + // Copy rest of message + data = data.slice(sizeOfMessage); + } catch (err) { + var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{ + sizeOfMessage:sizeOfMessage, + bytesRead:self.bytesRead, + stubBuffer:self.stubBuffer}}; + if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); + // We got a parse Error fire it off then keep going + self.emit("parseError", errorObject, self); + } + + } + } else { + // Create a buffer that contains the space for the non-complete message + self.stubBuffer = new Buffer(data.length) + // Copy the data to the stub buffer + data.copy(self.stubBuffer, 0); + // Exit parsing loop + data = new Buffer(0); + } + } + } + } + } +} + +var endHandler = function(self) { + return function() { + // Set connected to false + self.connected = false; + // Emit end event + self.emit("end", {err: 'connection received Fin packet from [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); + } +} + +var timeoutHandler = function(self) { + return function() { + self.emit("timeout", {err: 'connection to [' + self.socketOptions.host + ':' + self.socketOptions.port + '] timed out'}, self); + } +} + +var drainHandler = function(self) { + return function() { + } +} + +var errorHandler = function(self) { + return function(err) { + // Set connected to false + self.connected = false; + // Emit error + self.emit("error", {err: 'failed to connect to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); + } +} + +var closeHandler = function(self) { + return function(hadError) { + // If we have an error during the connection phase + if(hadError && !self.connected) { + // Set disconnected + self.connected = false; + // Emit error + self.emit("error", {err: 'failed to connect to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); + } else { + // Set disconnected + self.connected = false; + // Emit close + self.emit("close", {err: 'connection closed to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); + } + } +} + +// Some basic defaults +Connection.DEFAULT_PORT = 27017; + + + + + + + diff --git a/node_modules/mongodb/lib/mongodb/connection/connection_pool.js b/node_modules/mongodb/lib/mongodb/connection/connection_pool.js new file mode 100644 index 0000000..421d1bd --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/connection/connection_pool.js @@ -0,0 +1,259 @@ +var utils = require('./connection_utils'), + inherits = require('util').inherits, + net = require('net'), + EventEmitter = require('events').EventEmitter, + inherits = require('util').inherits, + MongoReply = require("../responses/mongo_reply").MongoReply, + Connection = require("./connection").Connection; + +var ConnectionPool = exports.ConnectionPool = function(host, port, poolSize, bson, socketOptions) { + if(typeof host !== 'string' || typeof port !== 'number') throw "host and port must be specified [" + host + ":" + port + "]"; + // Set up event emitter + EventEmitter.call(this); + // Keep all options for the socket in a specific collection allowing the user to specify the + // Wished upon socket connection parameters + this.socketOptions = typeof socketOptions === 'object' ? socketOptions : {}; + this.socketOptions.host = host; + this.socketOptions.port = port; + this.bson = bson; + // PoolSize is always + 1 for special reserved "measurment" socket (like ping, stats etc) + this.poolSize = poolSize; + + // Set default settings for the socket options + utils.setIntegerParameter(this.socketOptions, 'timeout', 0); + // Delay before writing out the data to the server + utils.setBooleanParameter(this.socketOptions, 'noDelay', true); + // Delay before writing out the data to the server + utils.setIntegerParameter(this.socketOptions, 'keepAlive', 0); + // Set the encoding of the data read, default is binary == null + utils.setStringParameter(this.socketOptions, 'encoding', null); + // Allows you to set a throttling bufferSize if you need to stop overflows + utils.setIntegerParameter(this.socketOptions, 'bufferSize', 0); + + // Internal structures + this.openConnections = []; + this.connections = []; + + // Assign connection id's + this.connectionId = 0; + + // Current index for selection of pool connection + this.currentConnectionIndex = 0; + // The pool state + this._poolState = 'disconnected'; + // timeout control + this._timeout = false; +} + +inherits(ConnectionPool, EventEmitter); + +ConnectionPool.prototype.setMaxBsonSize = function(maxBsonSize) { + if(maxBsonSize == null){ + maxBsonSize = Connection.DEFAULT_MAX_BSON_SIZE; + } + + for(var i = 0; i < this.openConnections.length; i++) { + this.openConnections[i].maxBsonSize = maxBsonSize; + } +} + +// Start a function +var _connect = function(_self) { + return new function() { + var connectionStatus = _self._poolState; + // Create a new connection instance + var connection = new Connection(_self.connectionId++, _self.socketOptions); + // Set logger on pool + connection.logger = _self.logger; + // Connect handler + connection.on("connect", function(err, connection) { + // Add connection to list of open connections + _self.openConnections.push(connection); + _self.connections.push(connection) + + // If the number of open connections is equal to the poolSize signal ready pool + if(_self.connections.length === _self.poolSize && _self._poolState !== 'disconnected') { + // Set connected + _self._poolState = 'connected'; + // Emit pool ready + _self.emit("poolReady"); + } else if(_self.connections.length < _self.poolSize) { + // We need to open another connection, make sure it's in the next + // tick so we don't get a cascade of errors + process.nextTick(function() { + _connect(_self); + }); + } + }); + + var numberOfErrors = 0 + + // Error handler + connection.on("error", function(err, connection) { + numberOfErrors++; + // If we are already disconnected ignore the event + if(connectionStatus != 'disconnected' && _self.listeners("error").length > 0) { + _self.emit("error", err); + } + + // Set disconnected + connectionStatus = 'disconnected'; + // Set disconnected + _self._poolState = 'disconnected'; + // Clean up + _self.openConnections = []; + _self.connections = []; + }); + + // Close handler + connection.on("close", function() { + // If we are already disconnected ignore the event + if(connectionStatus !== 'disconnected' && _self.listeners("close").length > 0) { + _self.emit("close"); + } + + // Set disconnected + connectionStatus = 'disconnected'; + // Set disconnected + _self._poolState = 'disconnected'; + // Clean up + _self.openConnections = []; + _self.connections = []; + }); + + // Timeout handler + connection.on("timeout", function(err, connection) { + // If we are already disconnected ignore the event + if(connectionStatus !== 'disconnected' && _self.listeners("timeout").length > 0) { + _self.emit("timeout", err); + } + + // Set disconnected + connectionStatus = 'disconnected'; + // Set disconnected + _self._poolState = 'disconnected'; + // Clean up + _self.openConnections = []; + _self.connections = []; + }); + + // Parse error, needs a complete shutdown of the pool + connection.on("parseError", function() { + // If we are already disconnected ignore the event + if(connectionStatus !== 'disconnected' && _self.listeners("parseError").length > 0) { + // if(connectionStatus == 'connected') { + _self.emit("parseError", new Error("parseError occured")); + } + + // Set disconnected + connectionStatus = 'disconnected'; + _self.stop(); + }); + + connection.on("message", function(message) { + _self.emit("message", message); + }); + + // Start connection in the next tick + connection.start(); + }(); +} + + +// Start method, will throw error if no listeners are available +// Pass in an instance of the listener that contains the api for +// finding callbacks for a given message etc. +ConnectionPool.prototype.start = function() { + var markerDate = new Date().getTime(); + var self = this; + + if(this.listeners("poolReady").length == 0) { + throw "pool must have at least one listener ready that responds to the [poolReady] event"; + } + + // Set pool state to connecting + this._poolState = 'connecting'; + this._timeout = false; + + _connect(self); +} + +// Restart a connection pool (on a close the pool might be in a wrong state) +ConnectionPool.prototype.restart = function() { + // Close all connections + this.stop(false); + // Now restart the pool + this.start(); +} + +// Stop the connections in the pool +ConnectionPool.prototype.stop = function(removeListeners) { + removeListeners = removeListeners == null ? true : removeListeners; + // Set disconnected + this._poolState = 'disconnected'; + + // Clear all listeners if specified + if(removeListeners) { + this.removeAllEventListeners(); + } + + // Close all connections + for(var i = 0; i < this.connections.length; i++) { + this.connections[i].close(); + } + + // Clean up + // this.connectionsWithErrors = []; + this.openConnections = []; + this.connections = []; +} + +// Check the status of the connection +ConnectionPool.prototype.isConnected = function() { + return this._poolState === 'connected'; +} + +// Checkout a connection from the pool for usage, or grab a specific pool instance +ConnectionPool.prototype.checkoutConnection = function(id) { + var index = (this.currentConnectionIndex++ % (this.openConnections.length)); + var connection = this.openConnections[index]; + return connection; +} + +ConnectionPool.prototype.getAllConnections = function() { + return this.connections; +} + +// Remove all non-needed event listeners +ConnectionPool.prototype.removeAllEventListeners = function() { + this.removeAllListeners("close"); + this.removeAllListeners("error"); + this.removeAllListeners("timeout"); + this.removeAllListeners("connect"); + this.removeAllListeners("end"); + this.removeAllListeners("parseError"); + this.removeAllListeners("message"); + this.removeAllListeners("poolReady"); +} + + + + + + + + + + + + + + + + + + + + + + diff --git a/node_modules/mongodb/lib/mongodb/connection/connection_utils.js b/node_modules/mongodb/lib/mongodb/connection/connection_utils.js new file mode 100644 index 0000000..5910924 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/connection/connection_utils.js @@ -0,0 +1,23 @@ +exports.setIntegerParameter = function(object, field, defaultValue) { + if(object[field] == null) { + object[field] = defaultValue; + } else if(typeof object[field] !== "number" && object[field] !== parseInt(object[field], 10)) { + throw "object field [" + field + "] must be a numeric integer value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; + } +} + +exports.setBooleanParameter = function(object, field, defaultValue) { + if(object[field] == null) { + object[field] = defaultValue; + } else if(typeof object[field] !== "boolean") { + throw "object field [" + field + "] must be a boolean value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; + } +} + +exports.setStringParameter = function(object, field, defaultValue) { + if(object[field] == null) { + object[field] = defaultValue; + } else if(typeof object[field] !== "string") { + throw "object field [" + field + "] must be a string value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; + } +} \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/connection/repl_set_servers.js b/node_modules/mongodb/lib/mongodb/connection/repl_set_servers.js new file mode 100644 index 0000000..3e0e15a --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/connection/repl_set_servers.js @@ -0,0 +1,972 @@ +var Connection = require('./connection').Connection, + DbCommand = require('../commands/db_command').DbCommand, + MongoReply = require('../responses/mongo_reply').MongoReply, + debug = require('util').debug, + EventEmitter = require('events').EventEmitter, + inherits = require('util').inherits, + inspect = require('util').inspect, + Server = require('./server').Server, + PingStrategy = require('./strategies/ping_strategy').PingStrategy, + StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy; + +const STATE_STARTING_PHASE_1 = 0; +const STATE_PRIMARY = 1; +const STATE_SECONDARY = 2; +const STATE_RECOVERING = 3; +const STATE_FATAL_ERROR = 4; +const STATE_STARTING_PHASE_2 = 5; +const STATE_UNKNOWN = 6; +const STATE_ARBITER = 7; +const STATE_DOWN = 8; +const STATE_ROLLBACK = 9; + +/** +* ReplSetServers constructor provides master-slave functionality +* +* @param serverArr{Array of type Server} +* @return constructor of ServerCluster +* +*/ +var ReplSetServers = exports.ReplSetServers = function(servers, options) { + // Set up event emitter + EventEmitter.call(this); + // Set up basic + if(!(this instanceof ReplSetServers)) return new ReplSetServers(server, options); + + var self = this; + // Contains the master server entry + this.options = options == null ? {} : options; + this.reconnectWait = this.options["reconnectWait"] != null ? this.options["reconnectWait"] : 1000; + this.retries = this.options["retries"] != null ? this.options["retries"] : 30; + this.replicaSet = this.options["rs_name"]; + + // Are we allowing reads from secondaries ? + this.readSecondary = this.options["read_secondary"]; + this.slaveOk = true; + this.closedConnectionCount = 0; + this._used = false; + + // Default poolSize for new server instances + this.poolSize = this.options.poolSize == null ? 1 : this.options.poolSize; + + // Set up ssl connections + this.ssl = this.options.ssl == null ? false : this.options.ssl; + + // Just keeps list of events we allow + this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[]}; + // Internal state of server connection + this._serverState = 'disconnected'; + // Read preference + this._readPreference = null; + // Do we record server stats or not + this.recordQueryStats = false; + + // Get the readPreference + var readPreference = this.options['readPreference']; + // Read preference setting + if(readPreference != null) { + if(readPreference != Server.READ_PRIMARY && readPreference != Server.READ_SECONDARY_ONLY + && readPreference != Server.READ_SECONDARY) { + throw new Error("Illegal readPreference mode specified, " + readPreference); + } + + // Set read Preference + this._readPreference = readPreference; + } else { + this._readPreference = null; + } + + // Strategy for picking a secondary + this.strategy = this.options['strategy'] == null ? 'statistical' : this.options['strategy']; + // Make sure strategy is one of the two allowed + if(this.strategy != null && (this.strategy != 'ping' && this.strategy != 'statistical')) throw new Error("Only ping or statistical strategies allowed"); + // Let's set up our strategy object for picking secodaries + if(this.strategy == 'ping') { + // Create a new instance + this.strategyInstance = new PingStrategy(this); + } else if(this.strategy == 'statistical') { + // Set strategy as statistical + this.strategyInstance = new StatisticsStrategy(this); + // Add enable query information + this.enableRecordQueryStats(true); + } + + // Set default connection pool options + this.socketOptions = this.options.socketOptions != null ? this.options.socketOptions : {}; + + // Set up logger if any set + this.logger = this.options.logger != null + && (typeof this.options.logger.debug == 'function') + && (typeof this.options.logger.error == 'function') + && (typeof this.options.logger.debug == 'function') + ? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; + + // Ensure all the instances are of type server and auto_reconnect is false + if(!Array.isArray(servers) || servers.length == 0) { + throw Error("The parameter must be an array of servers and contain at least one server"); + } else if(Array.isArray(servers) || servers.length > 0) { + var count = 0; + servers.forEach(function(server) { + if(server instanceof Server) count = count + 1; + // Ensure no server has reconnect on + server.options.auto_reconnect = false; + }); + + if(count < servers.length) { + throw Error("All server entries must be of type Server"); + } else { + this.servers = servers; + } + } + + // Auto Reconnect property + Object.defineProperty(this, "autoReconnect", { enumerable: true + , get: function () { + return true; + } + }); + + // Get Read Preference method + Object.defineProperty(this, "readPreference", { enumerable: true + , get: function () { + if(this._readPreference == null && this.readSecondary) { + return Server.READ_SECONDARY; + } else if(this._readPreference == null && !this.readSecondary) { + return Server.READ_PRIMARY; + } else { + return this._readPreference; + } + } + }); + + // Db Instances + Object.defineProperty(this, "dbInstances", {enumerable:true + , get: function() { + var servers = this.allServerInstances(); + return servers[0].dbInstances; + } + }) + + // Auto Reconnect property + Object.defineProperty(this, "host", { enumerable: true + , get: function () { + if (this.primary != null) return this.primary.host; + } + }); + + Object.defineProperty(this, "port", { enumerable: true + , get: function () { + if (this.primary != null) return this.primary.port; + } + }); + + Object.defineProperty(this, "read", { enumerable: true + , get: function () { + return this.secondaries.length > 0 ? this.secondaries[0] : null; + } + }); + + // Get list of secondaries + Object.defineProperty(this, "secondaries", {enumerable: true + , get: function() { + var keys = Object.keys(this._state.secondaries); + var array = new Array(keys.length); + // Convert secondaries to array + for(var i = 0; i < keys.length; i++) { + array[i] = this._state.secondaries[keys[i]]; + } + return array; + } + }); + + // Get list of all secondaries including passives + Object.defineProperty(this, "allSecondaries", {enumerable: true + , get: function() { + return this.secondaries.concat(this.passives); + } + }); + + // Get list of arbiters + Object.defineProperty(this, "arbiters", {enumerable: true + , get: function() { + var keys = Object.keys(this._state.arbiters); + var array = new Array(keys.length); + // Convert arbiters to array + for(var i = 0; i < keys.length; i++) { + array[i] = this._state.arbiters[keys[i]]; + } + return array; + } + }); + + // Get list of passives + Object.defineProperty(this, "passives", {enumerable: true + , get: function() { + var keys = Object.keys(this._state.passives); + var array = new Array(keys.length); + // Convert arbiters to array + for(var i = 0; i < keys.length; i++) { + array[i] = this._state.passives[keys[i]]; + } + return array; + } + }); + + // Master connection property + Object.defineProperty(this, "primary", { enumerable: true + , get: function () { + return this._state != null ? this._state.master : null; + } + }); +}; + +inherits(ReplSetServers, EventEmitter); + +// Allow setting the read preference at the replicaset level +ReplSetServers.prototype.setReadPreference = function(preference) { + // Set read preference + this._readPreference = preference; + // Ensure slaveOk is correct for secodnaries read preference and tags + if((this._readPreference == Server.READ_SECONDARY || this._readPreference == Server.READ_SECONDARY_ONLY) + || (this._readPreference != null && typeof this._readPreference == 'object')) { + this.slaveOk = true; + } +} + +// Return the used state +ReplSetServers.prototype._isUsed = function() { + return this._used; +} + +ReplSetServers.prototype.setTarget = function(target) { + this.target = target; +}; + +ReplSetServers.prototype.isConnected = function() { + // Return the state of the replicaset server + return this.primary != null && this._state.master != null && this._state.master.isConnected(); +} + +Server.prototype.isSetMember = function() { + return false; +} + +ReplSetServers.prototype.isPrimary = function(config) { + return this.readSecondary && this.secondaries.length > 0 ? false : true; +} + +ReplSetServers.prototype.isReadPrimary = ReplSetServers.prototype.isPrimary; + +// Clean up dead connections +var cleanupConnections = ReplSetServers.cleanupConnections = function(connections, addresses, byTags) { + // Ensure we don't have entries in our set with dead connections + var keys = Object.keys(connections); + for(var i = 0; i < keys.length; i++) { + var server = connections[keys[i]]; + // If it's not connected remove it from the list + if(!server.isConnected()) { + // Remove from connections and addresses + delete connections[keys[i]]; + delete addresses[keys[i]]; + // Clean up tags if needed + if(server.tags != null && typeof server.tags === 'object') { + cleanupTags(server, byTags); + } + } + } +} + +var cleanupTags = ReplSetServers._cleanupTags = function(server, byTags) { + var serverTagKeys = Object.keys(server.tags); + // Iterate over all server tags and remove any instances for that tag that matches the current + // server + for(var i = 0; i < serverTagKeys.length; i++) { + // Fetch the value for the tag key + var value = server.tags[serverTagKeys[i]]; + + // If we got an instance of the server + if(byTags[serverTagKeys[i]] != null + && byTags[serverTagKeys[i]][value] != null + && Array.isArray(byTags[serverTagKeys[i]][value])) { + // List of clean servers + var cleanInstances = []; + // We got instances for the particular tag set + var instances = byTags[serverTagKeys[i]][value]; + for(var j = 0; j < instances.length; j++) { + var serverInstance = instances[j]; + // If we did not find an instance add it to the clean instances + if((serverInstance.host + ":" + serverInstance.port) !== (server.host + ":" + server.port)) { + cleanInstances.push(serverInstance); + } + } + + // Update the byTags list + byTags[serverTagKeys[i]][value] = cleanInstances; + } + } +} + +ReplSetServers.prototype.allServerInstances = function() { + var self = this; + // Close all the servers (concatenate entire list of servers first for ease) + var allServers = self._state.master != null ? [self._state.master] : []; + + // Secondary keys + var keys = Object.keys(self._state.secondaries); + // Add all secondaries + for(var i = 0; i < keys.length; i++) { + allServers.push(self._state.secondaries[keys[i]]); + } + + // Arbiter keys + var keys = Object.keys(self._state.arbiters); + // Add all arbiters + for(var i = 0; i < keys.length; i++) { + allServers.push(self._state.arbiters[keys[i]]); + } + + // Passive keys + var keys = Object.keys(self._state.passives); + // Add all arbiters + for(var i = 0; i < keys.length; i++) { + allServers.push(self._state.passives[keys[i]]); + } + + // Return complete list of all servers + return allServers; +} + +// Ensure no callback is left hanging when we have an error +var __executeAllCallbacksWithError = function(dbInstance, error) { + var keys = Object.keys(dbInstance._callBackStore._notReplied); + // Iterate over all callbacks + for(var i = 0; i < keys.length; i++) { + // Delete info object + delete dbInstance._callBackStore._notReplied[keys[i]]; + // Emit the error + dbInstance._callBackStore.emit(keys[i], error); + } +} + +ReplSetServers.prototype.connect = function(parent, options, callback) { + var self = this; + var dateStamp = new Date().getTime(); + if('function' === typeof options) callback = options, options = {}; + if(options == null) options = {}; + if(!('function' === typeof callback)) callback = null; + + // Keep reference to parent + this.db = parent; + // Set server state to connecting + this._serverState = 'connecting'; + // Reference to the instance + var replSetSelf = this; + var serverConnections = this.servers; + // Ensure parent can do a slave query if it's set + parent.slaveOk = this.slaveOk ? this.slaveOk : parent.slaveOk; + // Number of total servers that need to initialized (known servers) + var numberOfServersLeftToInitialize = serverConnections.length; + + // Clean up state + replSetSelf._state = {'master':null, 'secondaries':{}, 'arbiters':{}, 'passives':{}, 'errors':{}, 'addresses':{}, 'byTags':{}, 'setName':null, 'errorMessages':[], 'members':[]}; + + // Create a connection handler + var connectionHandler = function(instanceServer) { + return function(err, result) { + // Don't attempt to connect if we are done + // if(replSetSelf._serverState === 'disconnected') return; + // Remove a server from the list of intialized servers we need to perform + numberOfServersLeftToInitialize = numberOfServersLeftToInitialize - 1; + // Add enable query information + instanceServer.enableRecordQueryStats(replSetSelf.recordQueryStats); + + if(err == null && result.documents[0].hosts != null) { + // Fetch the isMaster command result + var document = result.documents[0]; + // Break out the results + var setName = document.setName; + var isMaster = document.ismaster; + var secondary = document.secondary; + var passive = document.passive; + var arbiterOnly = document.arbiterOnly; + var hosts = Array.isArray(document.hosts) ? document.hosts : []; + var arbiters = Array.isArray(document.arbiters) ? document.arbiters : []; + var passives = Array.isArray(document.passives) ? document.passives : []; + var tags = document.tags ? document.tags : {}; + var primary = document.primary; + var me = document.me; + + // Only add server to our internal list if it's a master, secondary or arbiter + if(isMaster == true || secondary == true || arbiterOnly == true) { + // Handle a closed connection + var closeHandler = function(err, server) { + var closeServers = function() { + // Set the state to disconnected + parent._state = 'disconnected'; + // Shut down the replicaset for now and Fire off all the callbacks sitting with no reply + if(replSetSelf._serverState == 'connected') { + // Close the replicaset + replSetSelf.close(function() { + __executeAllCallbacksWithError(parent, err); + // Ensure single callback only + if(callback != null) { + // Single callback only + var internalCallback = callback; + callback = null; + // Return the error + internalCallback(err, null); + } else { + // If the parent has listeners trigger an event + if(parent.listeners("close").length > 0) { + parent.emit("close", err); + } + } + }); + } + } + + // Check if this is the primary server, then disconnect otherwise keep going + if(replSetSelf._state.master != null) { + var primaryAddress = replSetSelf._state.master.host + ":" + replSetSelf._state.master.port; + var errorServerAddress = server.host + ":" + server.port; + + // Only shut down the set if we have a primary server error + if(primaryAddress == errorServerAddress) { + closeServers(); + } else { + // Remove from the list of servers + delete replSetSelf._state.addresses[errorServerAddress]; + // Locate one of the lists and remove + if(replSetSelf._state.secondaries[errorServerAddress] != null) { + delete replSetSelf._state.secondaries[errorServerAddress]; + } else if(replSetSelf._state.arbiters[errorServerAddress] != null) { + delete replSetSelf._state.arbiters[errorServerAddress]; + } else if(replSetSelf._state.passives[errorServerAddress] != null) { + delete replSetSelf._state.passives[errorServerAddress]; + } + + // Check if we are reading from Secondary only + if(replSetSelf._readPreference == Server.READ_SECONDARY_ONLY && Object.keys(replSetSelf._state.secondaries).length == 0) { + closeServers(); + } + } + } else { + closeServers(); + } + } + + // Handle a connection timeout + var timeoutHandler = function(err, server) { + var closeServers = function() { + // Set the state to disconnected + parent._state = 'disconnected'; + // Shut down the replicaset for now and Fire off all the callbacks sitting with no reply + if(replSetSelf._serverState == 'connected') { + // Close the replicaset + replSetSelf.close(function() { + __executeAllCallbacksWithError(parent, err); + // Ensure single callback only + if(callback != null) { + // Single callback only + var internalCallback = callback; + callback = null; + // Return the error + internalCallback(new Error("connection timed out"), null); + } else { + // If the parent has listeners trigger an event + if(parent.listeners("error").length > 0) { + parent.emit("timeout", new Error("connection timed out")); + } + } + }); + } + } + + // Check if this is the primary server, then disconnect otherwise keep going + if(replSetSelf._state.master != null) { + var primaryAddress = replSetSelf._state.master.host + ":" + replSetSelf._state.master.port; + var errorServerAddress = server.host + ":" + server.port; + + // Only shut down the set if we have a primary server error + if(primaryAddress == errorServerAddress) { + closeServers(); + } else { + // Remove from the list of servers + delete replSetSelf._state.addresses[errorServerAddress]; + // Locate one of the lists and remove + if(replSetSelf._state.secondaries[errorServerAddress] != null) { + delete replSetSelf._state.secondaries[errorServerAddress]; + } else if(replSetSelf._state.arbiters[errorServerAddress] != null) { + delete replSetSelf._state.arbiters[errorServerAddress]; + } else if(replSetSelf._state.passives[errorServerAddress] != null) { + delete replSetSelf._state.passives[errorServerAddress]; + } + + // Check if we are reading from Secondary only + if(replSetSelf._readPreference == Server.READ_SECONDARY_ONLY && Object.keys(replSetSelf._state.secondaries).length == 0) { + closeServers(); + } + } + } else { + closeServers(); + } + } + + // Handle an error + var errorHandler = function(err, server) { + var closeServers = function() { + // Set the state to disconnected + parent._state = 'disconnected'; + // Shut down the replicaset for now and Fire off all the callbacks sitting with no reply + if(replSetSelf._serverState == 'connected') { + // Close the replicaset + replSetSelf.close(function() { + __executeAllCallbacksWithError(parent, err); + // Ensure single callback only + if(callback != null) { + // Single callback only + var internalCallback = callback; + callback = null; + // Return the error + internalCallback(err, null); + } else { + // If the parent has listeners trigger an event + if(parent.listeners("error").length > 0) { + parent.emit("error", err); + } + } + }); + } + } + + // Check if this is the primary server, then disconnect otherwise keep going + if(replSetSelf._state.master != null) { + var primaryAddress = replSetSelf._state.master.host + ":" + replSetSelf._state.master.port; + var errorServerAddress = server.host + ":" + server.port; + + // Only shut down the set if we have a primary server error + if(primaryAddress == errorServerAddress) { + closeServers(); + } else { + // Remove from the list of servers + delete replSetSelf._state.addresses[errorServerAddress]; + // Locate one of the lists and remove + if(replSetSelf._state.secondaries[errorServerAddress] != null) { + delete replSetSelf._state.secondaries[errorServerAddress]; + } else if(replSetSelf._state.arbiters[errorServerAddress] != null) { + delete replSetSelf._state.arbiters[errorServerAddress]; + } else if(replSetSelf._state.passives[errorServerAddress] != null) { + delete replSetSelf._state.passives[errorServerAddress]; + } + + // Check if we are reading from Secondary only + if(replSetSelf._readPreference == Server.READ_SECONDARY_ONLY && Object.keys(replSetSelf._state.secondaries).length == 0) { + closeServers(); + } + } + } else { + closeServers(); + } + } + + // Ensure we don't have duplicate handlers + instanceServer.removeAllListeners("close"); + instanceServer.removeAllListeners("error"); + instanceServer.removeAllListeners("timeout"); + + // Add error handler to the instance of the server + instanceServer.on("close", closeHandler); + // Add error handler to the instance of the server + instanceServer.on("error", errorHandler); + // instanceServer.on("timeout", errorHandler); + instanceServer.on("timeout", timeoutHandler); + // Add tag info + instanceServer.tags = tags; + + // For each tag in tags let's add the instance Server to the list for that tag + if(tags != null && typeof tags === 'object') { + var tagKeys = Object.keys(tags); + // For each tag file in the server add it to byTags + for(var i = 0; i < tagKeys.length; i++) { + var value = tags[tagKeys[i]]; + // Check if we have a top level tag object + if(replSetSelf._state.byTags[tagKeys[i]] == null) replSetSelf._state.byTags[tagKeys[i]] = {}; + // For the value check if we have an array of server instances + if(!Array.isArray(replSetSelf._state.byTags[tagKeys[i]][value])) replSetSelf._state.byTags[tagKeys[i]][value] = []; + // Check that the instance is not already registered there + var valueArray = replSetSelf._state.byTags[tagKeys[i]][value]; + var found = false; + + // Iterate over all values + for(var j = 0; j < valueArray.length; j++) { + if(valueArray[j].host == instanceServer.host && valueArray[j].port == instanceServer.port) { + found = true; + break; + } + } + + // If it was not found push the instance server to the list + if(!found) valueArray.push(instanceServer); + } + } + + // Remove from error list + delete replSetSelf._state.errors[me]; + + // Add our server to the list of finished servers + replSetSelf._state.addresses[me] = instanceServer; + + // Assign the set name + if(replSetSelf.replicaSet == null) { + replSetSelf._state.setName = setName; + } else if(replSetSelf.replicaSet != setName && replSetSelf._serverState != 'disconnected') { + replSetSelf._state.errorMessages.push(new Error("configured mongodb replicaset does not match provided replicaset [" + setName + "] != [" + replSetSelf.replicaSet + "]")); + // Set done + replSetSelf._serverState = 'disconnected'; + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Return error message ignoring rest of calls + return internalCallback(replSetSelf._state.errorMessages[0], parent); + } + + // Let's add the server to our list of server types + if(secondary == true && (passive == false || passive == null)) { + replSetSelf._state.secondaries[me] = instanceServer; + } else if(arbiterOnly == true) { + replSetSelf._state.arbiters[me] = instanceServer; + } else if(secondary == true && passive == true) { + replSetSelf._state.passives[me] = instanceServer; + } else if(isMaster == true) { + replSetSelf._state.master = instanceServer; + } else if(isMaster == false && primary != null && replSetSelf._state.addresses[primary]) { + replSetSelf._state.master = replSetSelf._state.addresses[primary]; + } + + // Let's go throught all the "possible" servers in the replicaset + var candidateServers = hosts.concat(arbiters).concat(passives); + + // If we have new servers let's add them + for(var i = 0; i < candidateServers.length; i++) { + // Fetch the server string + var candidateServerString = candidateServers[i]; + // Add the server if it's not defined + if(replSetSelf._state.addresses[candidateServerString] == null) { + // Split the server string + var parts = candidateServerString.split(/:/); + if(parts.length == 1) { + parts = [parts[0], Connection.DEFAULT_PORT]; + } + + // Default empty socket options object + var socketOptions = {}; + // If a socket option object exists clone it + if(replSetSelf.socketOptions != null) { + var keys = Object.keys(replSetSelf.socketOptions); + for(var i = 0; i < keys.length;i++) socketOptions[keys[i]] = replSetSelf.socketOptions[keys[i]]; + } + + // Add host information to socket options + socketOptions['host'] = parts[0]; + socketOptions['port'] = parseInt(parts[1]); + + // Create a new server instance + var newServer = new Server(parts[0], parseInt(parts[1]), {auto_reconnect:false, 'socketOptions':socketOptions + , logger:replSetSelf.logger, ssl:replSetSelf.ssl, poolSize:replSetSelf.poolSize}); + // Set the replicaset instance + newServer.replicasetInstance = replSetSelf; + + // Add handlers + newServer.on("close", closeHandler); + newServer.on("timeout", timeoutHandler); + newServer.on("error", errorHandler); + + // Add server to list, ensuring we don't get a cascade of request to the same server + replSetSelf._state.addresses[candidateServerString] = newServer; + + // Add a new server to the total number of servers that need to initialized before we are done + numberOfServersLeftToInitialize = numberOfServersLeftToInitialize + 1; + + // Let's set up a new server instance + newServer.connect(parent, {returnIsMasterResults: true, eventReceiver:newServer}, connectionHandler(newServer)); + } + } + } else { + // Remove the instance from out list of servers + delete replSetSelf._state.addresses[me]; + } + } + + // If done finish up + if((numberOfServersLeftToInitialize == 0) && replSetSelf._serverState === 'connecting' && replSetSelf._state.errorMessages.length == 0) { + // Set db as connected + replSetSelf._serverState = 'connected'; + // If we don't expect a master let's call back, otherwise we need a master before + // the connection is successful + if(replSetSelf.masterNotNeeded || replSetSelf._state.master != null) { + // If we have a read strategy boot it + if(replSetSelf.strategyInstance != null) { + // Ensure we have a proper replicaset defined + replSetSelf.strategyInstance.replicaset = replSetSelf; + // Start strategy + replSetSelf.strategyInstance.start(function(err) { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(null, parent); + }) + } else { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(null, parent); + } + } else if(replSetSelf.readSecondary == true && Object.keys(replSetSelf._state.secondaries).length > 0) { + // If we have a read strategy boot it + if(replSetSelf.strategyInstance != null) { + // Ensure we have a proper replicaset defined + replSetSelf.strategyInstance.replicaset = replSetSelf; + // Start strategy + replSetSelf.strategyInstance.start(function(err) { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(null, parent); + }) + } else { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(null, parent); + } + } else if(replSetSelf.readSecondary == true && Object.keys(replSetSelf._state.secondaries).length == 0) { + replSetSelf._serverState = 'disconnected'; + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Force close all server instances + replSetSelf.close(); + // Perform callback + internalCallback(new Error("no secondary server found"), null); + } else if(typeof callback === 'function'){ + replSetSelf._serverState = 'disconnected'; + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Force close all server instances + replSetSelf.close(); + // Perform callback + internalCallback(new Error("no primary server found"), null); + } + } else if((numberOfServersLeftToInitialize == 0) && replSetSelf._state.errorMessages.length > 0 && replSetSelf._serverState != 'disconnected') { + // Set done + replSetSelf._serverState = 'disconnected'; + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Force close all server instances + replSetSelf.close(); + // Callback to signal we are done + internalCallback(replSetSelf._state.errorMessages[0], null); + } + } + } + + // Ensure we have all registered servers in our set + for(var i = 0; i < serverConnections.length; i++) { + replSetSelf._state.addresses[serverConnections[i].host + ':' + serverConnections[i].port] = serverConnections[i]; + } + + // Initialize all the connections + for(var i = 0; i < serverConnections.length; i++) { + // Set up the logger for the server connection + serverConnections[i].logger = replSetSelf.logger; + // Default empty socket options object + var socketOptions = {}; + // If a socket option object exists clone it + if(this.socketOptions != null && typeof this.socketOptions === 'object') { + var keys = Object.keys(this.socketOptions); + for(var j = 0; j < keys.length;j++) socketOptions[keys[j]] = this.socketOptions[keys[j]]; + } + + // If ssl is specified + if(replSetSelf.ssl) serverConnections[i].ssl = true; + + // Add host information to socket options + socketOptions['host'] = serverConnections[i].host; + socketOptions['port'] = serverConnections[i].port; + + // Set the socket options + serverConnections[i].socketOptions = socketOptions; + // Set the replicaset instance + serverConnections[i].replicasetInstance = replSetSelf; + // Connect to server + serverConnections[i].connect(parent, {returnIsMasterResults: true, eventReceiver:serverConnections[i]}, connectionHandler(serverConnections[i])); + } + + // Check if we have an error in the inital set of servers and callback with error + if(replSetSelf._state.errorMessages.length > 0 && typeof callback === 'function') { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(replSetSelf._state.errorMessages[0], null); + } +} + +ReplSetServers.prototype.checkoutWriter = function() { + // Establish connection + var connection = this._state.master != null ? this._state.master.checkoutWriter() : null; + // Return the connection + return connection; +} + +ReplSetServers.prototype.checkoutReader = function() { + var connection = null; + // If we have specified to read from a secondary server grab a random one and read + // from it, otherwise just pass the primary connection + if((this.readSecondary == true || this._readPreference == Server.READ_SECONDARY || this._readPreference == Server.READ_SECONDARY_ONLY) && Object.keys(this._state.secondaries).length > 0) { + // Checkout a secondary server from the passed in set of servers + if(this.strategyInstance != null) { + connection = this.strategyInstance.checkoutSecondary(); + } else { + // Pick a random key + var keys = Object.keys(this._state.secondaries); + var key = keys[Math.floor(Math.random() * keys.length)]; + connection = this._state.secondaries[key].checkoutReader(); + } + } else if(this._readPreference == Server.READ_SECONDARY_ONLY && Object.keys(this._state.secondaries).length == 0) { + connection = null; + } else if(this._readPreference != null && typeof this._readPreference === 'object') { + // Get all tag keys (used to try to find a server that is valid) + var keys = Object.keys(this._readPreference); + // final instance server + var instanceServer = null; + // for each key look for an avilable instance + for(var i = 0; i < keys.length; i++) { + // Grab subkey value + var value = this._readPreference[keys[i]]; + + // Check if we have any servers for the tag, if we do pick a random one + if(this._state.byTags[keys[i]] != null + && this._state.byTags[keys[i]][value] != null + && Array.isArray(this._state.byTags[keys[i]][value]) + && this._state.byTags[keys[i]][value].length > 0) { + // Let's grab an available server from the list using a random pick + var serverInstances = this._state.byTags[keys[i]][value]; + // Set instance to return + instanceServer = serverInstances[Math.floor(Math.random() * serverInstances.length)]; + break; + } + } + + // Return the instance of the server + connection = instanceServer != null ? instanceServer.checkoutReader() : this.checkoutWriter(); + } else { + connection = this.checkoutWriter(); + } + // Return the connection + return connection; +} + +ReplSetServers.prototype.allRawConnections = function() { + // Neeed to build a complete list of all raw connections, start with master server + var allConnections = []; + // Get connection object + var allMasterConnections = this._state.master.connectionPool.getAllConnections(); + // Add all connections to list + allConnections = allConnections.concat(allMasterConnections); + + // If we have read secondary let's add all secondary servers + if(this.readSecondary && Object.keys(this._state.secondaries).length > 0) { + // Get all the keys + var keys = Object.keys(this._state.secondaries); + // For each of the secondaries grab the connections + for(var i = 0; i < keys.length; i++) { + // Get connection object + var secondaryPoolConnections = this._state.secondaries[keys[i]].connectionPool.getAllConnections(); + // Add all connections to list + allConnections = allConnections.concat(secondaryPoolConnections); + } + } + + // Return all the conections + return allConnections; +} + +ReplSetServers.prototype.enableRecordQueryStats = function(enable) { + // Set the global enable record query stats + this.recordQueryStats = enable; + // Ensure all existing servers already have the flag set, even if the + // connections are up already or we have not connected yet + if(this._state != null && this._state.addresses != null) { + var keys = Object.keys(this._state.addresses); + // Iterate over all server instances and set the enableRecordQueryStats flag + for(var i = 0; i < keys.length; i++) { + this._state.addresses[keys[i]].enableRecordQueryStats(enable); + } + } else if(Array.isArray(this.servers)) { + for(var i = 0; i < this.servers.length; i++) { + this.servers[i].enableRecordQueryStats(enable); + } + } +} + +ReplSetServers.prototype.disconnect = function(callback) { + this.close(callback); +} + +ReplSetServers.prototype.close = function(callback) { + var self = this; + // Set server status as disconnected + this._serverState = 'disconnected'; + // Get all the server instances and close them + var allServers = []; + // Make sure we have servers + if(this._state['addresses'] != null) { + var keys = Object.keys(this._state.addresses); + for(var i = 0; i < keys.length; i++) { + allServers.push(this._state.addresses[keys[i]]); + } + } + + // Let's process all the closing + var numberOfServersToClose = allServers.length; + + // Remove all the listeners + self.removeAllListeners(); + + // Special case where there are no servers + if(allServers.length == 0 && typeof callback === 'function') return callback(null, null); + + // Close the servers + for(var i = 0; i < allServers.length; i++) { + var server = allServers[i]; + if(server.isConnected()) { + // Close each server + server.close(function() { + numberOfServersToClose = numberOfServersToClose - 1; + // Clear out state if we are done + if(numberOfServersToClose == 0) { + // Clear out state + self._state = {'master':null, 'secondaries':{}, 'arbiters':{}, 'passives':{}, 'errors':{}, 'addresses':{}, 'byTags':{}, 'setName':null, 'errorMessages':[], 'members':[]}; + } + + // If we are finished perform the call back + if(numberOfServersToClose == 0 && typeof callback === 'function') { + callback(null); + } + }) + } else { + numberOfServersToClose = numberOfServersToClose - 1; + // If we have no more servers perform the callback + if(numberOfServersToClose == 0 && typeof callback === 'function') { + callback(null); + } + } + } +} \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/connection/server.js b/node_modules/mongodb/lib/mongodb/connection/server.js new file mode 100644 index 0000000..80c3b27 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/connection/server.js @@ -0,0 +1,640 @@ +var Connection = require('./connection').Connection, + DbCommand = require('../commands/db_command').DbCommand, + MongoReply = require('../responses/mongo_reply').MongoReply, + ConnectionPool = require('./connection_pool').ConnectionPool, + EventEmitter = require('events').EventEmitter, + MongoReply = require("../responses/mongo_reply").MongoReply, + inherits = require('util').inherits; + +var Server = exports.Server = function(host, port, options) { + // Set up event emitter + EventEmitter.call(this); + // Set up Server instance + if(!(this instanceof Server)) return new Server(host, port, options); + + var self = this; + this.host = host; + this.port = port; + this.options = options == null ? {} : options; + this.internalConnection; + this.internalMaster = false; + this.connected = false; + this.poolSize = this.options.poolSize == null ? 1 : this.options.poolSize; + this.ssl = this.options.ssl == null ? false : this.options.ssl; + this.slaveOk = this.options["slave_ok"]; + this._used = false; + + // Get the readPreference + var readPreference = this.options['readPreference']; + // Read preference setting + if(readPreference != null) { + if(readPreference != Server.READ_PRIMARY && readPreference != Server.READ_SECONDARY_ONLY + && readPreference != Server.READ_SECONDARY) { + throw new Error("Illegal readPreference mode specified, " + readPreference); + } + + // Set read Preference + this._readPreference = readPreference; + } else { + this._readPreference = null; + } + + // Contains the isMaster information returned from the server + this.isMasterDoc; + + // Set default connection pool options + this.socketOptions = this.options.socketOptions != null ? this.options.socketOptions : {}; + // Set ssl up if it's defined + if(this.ssl) { + this.socketOptions.ssl = true; + } + + // Set up logger if any set + this.logger = this.options.logger != null + && (typeof this.options.logger.debug == 'function') + && (typeof this.options.logger.error == 'function') + && (typeof this.options.logger.debug == 'function') + ? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; + + // Just keeps list of events we allow + this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[]}; + // Internal state of server connection + this._serverState = 'disconnected'; + // this._timeout = false; + // Contains state information about server connection + this._state = {'runtimeStats': {'queryStats':new RunningStats()}}; + // Do we record server stats or not + this.recordQueryStats = false; + + // Setters and getters + Object.defineProperty(this, "autoReconnect", { enumerable: true + , get: function () { + return this.options['auto_reconnect'] == null ? false : this.options['auto_reconnect']; + } + }); + + Object.defineProperty(this, "connection", { enumerable: true + , get: function () { + return this.internalConnection; + } + , set: function(connection) { + this.internalConnection = connection; + } + }); + + Object.defineProperty(this, "master", { enumerable: true + , get: function () { + return this.internalMaster; + } + , set: function(value) { + this.internalMaster = value; + } + }); + + Object.defineProperty(this, "primary", { enumerable: true + , get: function () { + return this; + } + }); + + // Getter for query Stats + Object.defineProperty(this, "queryStats", { enumerable: true + , get: function () { + return this._state.runtimeStats.queryStats; + } + }); + + Object.defineProperty(this, "runtimeStats", { enumerable: true + , get: function () { + return this._state.runtimeStats; + } + }); + + // Get Read Preference method + Object.defineProperty(this, "readPreference", { enumerable: true + , get: function () { + if(this._readPreference == null && this.readSecondary) { + return Server.READ_SECONDARY; + } else if(this._readPreference == null && !this.readSecondary) { + return Server.READ_PRIMARY; + } else { + return this._readPreference; + } + } + }); +}; + +// Inherit simple event emitter +inherits(Server, EventEmitter); +// Read Preferences +Server.READ_PRIMARY = 'primary'; +Server.READ_SECONDARY = 'secondary'; +Server.READ_SECONDARY_ONLY = 'secondaryOnly'; + +// Always ourselves +Server.prototype.setReadPreference = function() {} + +// Return the used state +Server.prototype._isUsed = function() { + return this._used; +} + +// Server close function +Server.prototype.close = function(callback) { + // Remove all local listeners + this.removeAllListeners(); + + if(this.connectionPool != null) { + // Remove all the listeners on the pool so it does not fire messages all over the place + this.connectionPool.removeAllEventListeners(); + // Close the connection if it's open + this.connectionPool.stop(); + } + + // Set server status as disconnected + this._serverState = 'disconnected'; + // Peform callback if present + if(typeof callback === 'function') callback(); +}; + +Server.prototype.isConnected = function() { + return this.connectionPool != null && this.connectionPool.isConnected(); +} + +Server.prototype.allServerInstances = function() { + return [this]; +} + +Server.prototype.isSetMember = function() { + return this['replicasetInstance'] != null; +} + +Server.prototype.connect = function(dbInstance, options, callback) { + if('function' === typeof options) callback = options, options = {}; + if(options == null) options = {}; + if(!('function' === typeof callback)) callback = null; + + // Currently needed to work around problems with multiple connections in a pool with ssl + // TODO fix if possible + if(this.ssl == true) { + // Set up socket options for ssl + this.socketOptions.ssl = true; + } + + // Let's connect + var server = this; + // Let's us override the main receiver of events + var eventReceiver = options.eventReceiver != null ? options.eventReceiver : this; + // Creating dbInstance + this.dbInstance = dbInstance; + // Save reference to dbInstance + this.dbInstances = [dbInstance]; + + // Set server state to connecting + this._serverState = 'connecting'; + // Ensure dbInstance can do a slave query if it's set + dbInstance.slaveOk = this.slaveOk ? this.slaveOk : dbInstance.slaveOk; + // Create connection Pool instance with the current BSON serializer + var connectionPool = new ConnectionPool(this.host, this.port, this.poolSize, dbInstance.bson, this.socketOptions); + // Set logger on pool + connectionPool.logger = this.logger; + + // Set up a new pool using default settings + server.connectionPool = connectionPool; + + // Set basic parameters passed in + var returnIsMasterResults = options.returnIsMasterResults == null ? false : options.returnIsMasterResults; + + // Create a default connect handler, overriden when using replicasets + var connectCallback = function(err, reply) { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // If something close down the connection and removed the callback before + // proxy killed connection etc, ignore the erorr as close event was isssued + if(err != null && internalCallback == null) return; + // Internal callback + if(err != null) return internalCallback(err, null); + server.master = reply.documents[0].ismaster == 1 ? true : false; + server.connectionPool.setMaxBsonSize(reply.documents[0].maxBsonObjectSize); + // Set server as connected + server.connected = true; + // Save document returned so we can query it + server.isMasterDoc = reply.documents[0]; + + // If we have it set to returnIsMasterResults + if(returnIsMasterResults) { + internalCallback(null, reply); + } else { + internalCallback(null, dbInstance); + } + }; + + // Let's us override the main connect callback + var connectHandler = options.connectHandler == null ? connectCallback : options.connectHandler; + + // Set up on connect method + connectionPool.on("poolReady", function() { + // Create db command and Add the callback to the list of callbacks by the request id (mapping outgoing messages to correct callbacks) + var db_command = DbCommand.NcreateIsMasterCommand(dbInstance, dbInstance.databaseName); + // Check out a reader from the pool + var connection = connectionPool.checkoutConnection(); + // Set server state to connected + server._serverState = 'connected'; + + // Register handler for messages + dbInstance._registerHandler(db_command, false, connection, connectHandler); + + // Write the command out + connection.write(db_command); + }) + + // Set up item connection + connectionPool.on("message", function(message) { + // Attempt to parse the message + try { + // Create a new mongo reply + var mongoReply = new MongoReply() + // Parse the header + mongoReply.parseHeader(message, connectionPool.bson) + // If message size is not the same as the buffer size + // something went terribly wrong somewhere + if(mongoReply.messageLength != message.length) { + // Emit the error + eventReceiver.emit("error", new Error("bson length is different from message length"), server); + // Remove all listeners + server.removeAllListeners(); + } else { + var startDate = new Date().getTime(); + + // Callback instance + var callbackInfo = null; + var dbInstanceObject = null; + + // Locate a callback instance and remove any additional ones + for(var i = 0; i < server.dbInstances.length; i++) { + var dbInstanceObjectTemp = server.dbInstances[i]; + var hasHandler = dbInstanceObjectTemp._hasHandler(mongoReply.responseTo.toString()); + // Assign the first one we find and remove any duplicate ones + if(hasHandler && callbackInfo == null) { + callbackInfo = dbInstanceObjectTemp._findHandler(mongoReply.responseTo.toString()); + dbInstanceObject = dbInstanceObjectTemp; + } else if(hasHandler) { + dbInstanceObjectTemp._removeHandler(mongoReply.responseTo.toString()); + } + } + + // Only execute callback if we have a caller + if(callbackInfo.callback && Array.isArray(callbackInfo.info.chained)) { + // Check if callback has already been fired (missing chain command) + var chained = callbackInfo.info.chained; + var numberOfFoundCallbacks = 0; + for(var i = 0; i < chained.length; i++) { + if(dbInstanceObject._hasHandler(chained[i])) numberOfFoundCallbacks++; + } + + // If we have already fired then clean up rest of chain and move on + if(numberOfFoundCallbacks != chained.length) { + for(var i = 0; i < chained.length; i++) { + dbInstanceObject._removeHandler(chained[i]); + } + + // Just return from function + return; + } + + // Parse the body + mongoReply.parseBody(message, connectionPool.bson, callbackInfo.info.raw, function(err) { + var callbackInfo = dbInstanceObject._findHandler(mongoReply.responseTo.toString()); + // If we have an error let's execute the callback and clean up all other + // chained commands + var firstResult = mongoReply && mongoReply.documents; + // Check for an error, if we have one let's trigger the callback and clean up + // The chained callbacks + if(firstResult[0].err != null || firstResult[0].errmsg != null) { + // Trigger the callback for the error + dbInstanceObject._callHandler(mongoReply.responseTo, mongoReply, null); + } else { + var chainedIds = callbackInfo.info.chained; + + if(chainedIds.length > 0 && chainedIds[chainedIds.length - 1] == mongoReply.responseTo) { + // Cleanup all other chained calls + chainedIds.pop(); + // Remove listeners + for(var i = 0; i < chainedIds.length; i++) dbInstanceObject._removeHandler(chainedIds[i]); + // Call the handler + dbInstanceObject._callHandler(mongoReply.responseTo, callbackInfo.info.results.shift(), null); + } else{ + // Add the results to all the results + for(var i = 0; i < chainedIds.length; i++) { + var handler = dbInstanceObject._findHandler(chainedIds[i]); + // Check if we have an object, if it's the case take the current object commands and + // and add this one + if(handler.info != null) { + handler.info.results = Array.isArray(callbackInfo.info.results) ? callbackInfo.info.results : []; + handler.info.results.push(mongoReply); + } + } + } + } + }); + } else if(callbackInfo.callback) { + // Parse the body + mongoReply.parseBody(message, connectionPool.bson, callbackInfo.info.raw, function(err) { + // Let's record the stats info if it's enabled + if(server.recordQueryStats == true && server._state['runtimeStats'] != null + && server._state.runtimeStats['queryStats'] instanceof RunningStats) { + // Add data point to the running statistics object + server._state.runtimeStats.queryStats.push(new Date().getTime() - callbackInfo.info.start); + } + + // Trigger the callback + dbInstanceObject._callHandler(mongoReply.responseTo, mongoReply, null); + }); + } + } + } catch (err) { + // Throw error in next tick + process.nextTick(function() { + throw err; + }) + } + }); + + // Handle timeout + connectionPool.on("timeout", function(err) { + // If pool connection is already closed + if(server._serverState === 'disconnected') return; + // Set server state to disconnected + server._serverState = 'disconnected'; + // Close the pool + connectionPool.stop(); + // If we have a callback return the error + if(typeof callback === 'function') { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(err, null); + } else if(server.isSetMember()) { + server.emit("timeout", err, server); + } else { + eventReceiver.emit("timeout", err, server); + } + + // If we are a single server connection fire errors correctly + if(!server.isSetMember()) { + // Fire all callback errors + _fireCallbackErrors(server, err); + // Emit error + _emitAcrossAllDbInstances(server, eventReceiver, "timeout", err, server); + } + }); + + // Handle errors + connectionPool.on("error", function(message) { + // If pool connection is already closed + if(server._serverState === 'disconnected') return; + // Set server state to disconnected + server._serverState = 'disconnected'; + // Close the pool + connectionPool.stop(); + // If we have a callback return the error + if(typeof callback === 'function') {// && !server.isSetMember()) { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(new Error(message && message.err ? message.err : message), null); + } else if(server.isSetMember()) { + server.emit("error", new Error(message && message.err ? message.err : message), server); + } else { + eventReceiver.emit("error", new Error(message && message.err ? message.err : message), server); + } + + // If we are a single server connection fire errors correctly + if(!server.isSetMember()) { + // Fire all callback errors + _fireCallbackErrors(server, new Error(message && message.err ? message.err : message)); + // Emit error + _emitAcrossAllDbInstances(server, eventReceiver, "error", new Error(message && message.err ? message.err : message), server); + } + }); + + // Handle close events + connectionPool.on("close", function() { + // If pool connection is already closed + if(server._serverState === 'disconnected') return; + // Set server state to disconnected + server._serverState = 'disconnected'; + // Close the pool + connectionPool.stop(true); + // If we have a callback return the error + if(typeof callback == 'function') { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(new Error("connection closed"), null); + } else if(server.isSetMember()) { + server.emit("close", new Error("connection closed"), server); + } else { + eventReceiver.emit("close", new Error("connection closed"), server); + } + + // If we are a single server connection fire errors correctly + if(!server.isSetMember()) { + // Fire all callback errors + _fireCallbackErrors(server, new Error("connection closed")); + // Emit error + _emitAcrossAllDbInstances(server, eventReceiver, "close", server); + } + }); + + // If we have a parser error we are in an unknown state, close everything and emit + // error + connectionPool.on("parseError", function(message) { + // If pool connection is already closed + if(server._serverState === 'disconnected') return; + // Set server state to disconnected + server._serverState = 'disconnected'; + // Close the pool + connectionPool.stop(); + // If we have a callback return the error + if(typeof callback === 'function') { + // ensure no callbacks get called twice + var internalCallback = callback; + callback = null; + // Perform callback + internalCallback(new Error("connection closed due to parseError"), null); + } else if(server.isSetMember()) { + server.emit("parseError", new Error("connection closed due to parseError"), server); + } else { + eventReceiver.emit("parseError", new Error("connection closed due to parseError"), server); + } + + // If we are a single server connection fire errors correctly + if(!server.isSetMember()) { + // Fire all callback errors + _fireCallbackErrors(server, new Error("connection closed due to parseError")); + // Emit error + _emitAcrossAllDbInstances(server, eventReceiver, "parseError", server); + } + }); + + // Boot up connection poole, pass in a locator of callbacks + connectionPool.start(); +} + +// Fire all the errors +var _fireCallbackErrors = function(server, err) { + // Locate all the possible callbacks that need to return + for(var i = 0; i < server.dbInstances.length; i++) { + // Fetch the db Instance + var dbInstance = server.dbInstances[i]; + // Check all callbacks + var keys = Object.keys(dbInstance._callBackStore._notReplied); + // For each key check if it's a callback that needs to be returned + for(var j = 0; j < keys.length; j++) { + var info = dbInstance._callBackStore._notReplied[keys[j]]; + if(info.connection.socketOptions.host === server.host && info.connection.socketOptions.port === server.port) { + dbInstance._callBackStore.emit(keys[j], err, null); + } + } + } +} + +var _emitAcrossAllDbInstances = function(server, filterDb, event, message, object) { + // Emit close event across all db instances sharing the sockets + var allServerInstances = server.allServerInstances(); + // Fetch the first server instance + var serverInstance = allServerInstances[0]; + // For all db instances signal all db instances + if(Array.isArray(serverInstance.dbInstances) && serverInstance.dbInstances.length > 1) { + for(var i = 0; i < serverInstance.dbInstances.length; i++) { + var dbInstance = serverInstance.dbInstances[i]; + // Check if it's our current db instance and skip if it is + if(filterDb == null || filterDb.databaseName !== dbInstance.databaseName || filterDb.tag !== dbInstance.tag) { + dbInstance.emit(event, message, object); + } + } + } +} + +Server.prototype.allRawConnections = function() { + return this.connectionPool.getAllConnections(); +} + +// Check if a writer can be provided +var canCheckoutWriter = function(self, read) { + // We cannot write to an arbiter or secondary server + if(self.isMasterDoc['arbiterOnly'] == true) { + return new Error("Cannot write to an arbiter"); + } if(self.isMasterDoc['secondary'] == true) { + return new Error("Cannot write to a secondary"); + } else if(read == true && self._readPreference == Server.READ_SECONDARY_ONLY && self.isMasterDoc['ismaster'] == true) { + return new Error("Cannot read from primary when secondary only specified"); + } + + // Return no error + return null; +} + +Server.prototype.checkoutWriter = function(read) { + if(read == true) return this.connectionPool.checkoutConnection(); + // Check if are allowed to do a checkout (if we try to use an arbiter f.ex) + var result = canCheckoutWriter(this, read); + // If the result is null check out a writer + if(result == null) { + return this.connectionPool.checkoutConnection(); + } else { + return result; + } +} + +// Check if a reader can be provided +var canCheckoutReader = function(self) { + // We cannot write to an arbiter or secondary server + if(self.isMasterDoc['arbiterOnly'] == true) { + return new Error("Cannot write to an arbiter"); + } else if(self._readPreference != null) { + // If the read preference is Primary and the instance is not a master return an error + if(self._readPreference == Server.READ_PRIMARY && self.isMasterDoc['ismaster'] != true) { + return new Error("Read preference is " + Server.READ_PRIMARY + " and server is not master"); + } else if(self._readPreference == Server.READ_SECONDARY_ONLY && self.isMasterDoc['ismaster'] == true) { + return new Error("Cannot read from primary when secondary only specified"); + } + } + + // Return no error + return null; +} + +Server.prototype.checkoutReader = function() { + // Check if are allowed to do a checkout (if we try to use an arbiter f.ex) + var result = canCheckoutReader(this); + // If the result is null check out a writer + if(result == null) { + return this.connectionPool.checkoutConnection(); + } else { + return result; + } +} + +Server.prototype.enableRecordQueryStats = function(enable) { + this.recordQueryStats = enable; +} + +// +// Internal statistics object used for calculating average and standard devitation on +// running queries +var RunningStats = function() { + var self = this; + this.m_n = 0; + this.m_oldM = 0.0; + this.m_oldS = 0.0; + this.m_newM = 0.0; + this.m_newS = 0.0; + + // Define getters + Object.defineProperty(this, "numDataValues", { enumerable: true + , get: function () { return this.m_n; } + }); + + Object.defineProperty(this, "mean", { enumerable: true + , get: function () { return (this.m_n > 0) ? this.m_newM : 0.0; } + }); + + Object.defineProperty(this, "variance", { enumerable: true + , get: function () { return ((this.m_n > 1) ? this.m_newS/(this.m_n - 1) : 0.0); } + }); + + Object.defineProperty(this, "standardDeviation", { enumerable: true + , get: function () { return Math.sqrt(this.variance); } + }); + + Object.defineProperty(this, "sScore", { enumerable: true + , get: function () { + var bottom = this.mean + this.standardDeviation; + if(bottom == 0) return 0; + return ((2 * this.mean * this.standardDeviation)/(bottom)); + } + }); +} + +RunningStats.prototype.push = function(x) { + // Update the number of samples + this.m_n = this.m_n + 1; + // See Knuth TAOCP vol 2, 3rd edition, page 232 + if(this.m_n == 1) { + this.m_oldM = this.m_newM = x; + this.m_oldS = 0.0; + } else { + this.m_newM = this.m_oldM + (x - this.m_oldM) / this.m_n; + this.m_newS = this.m_oldS + (x - this.m_oldM) * (x - this.m_newM); + + // set up for next iteration + this.m_oldM = this.m_newM; + this.m_oldS = this.m_newS; + } +} diff --git a/node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js b/node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js new file mode 100644 index 0000000..6bb36cf --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js @@ -0,0 +1,125 @@ +var Server = require("../server").Server; + +// The ping strategy uses pings each server and records the +// elapsed time for the server so it can pick a server based on lowest +// return time for the db command {ping:true} +var PingStrategy = exports.PingStrategy = function(replicaset) { + this.replicaset = replicaset; + this.state = 'disconnected'; + // Class instance + this.Db = require("../../db").Db; +} + +// Starts any needed code +PingStrategy.prototype.start = function(callback) { + this.state = 'connected'; + // Start ping server + this._pingServer(callback); +} + +// Stops and kills any processes running +PingStrategy.prototype.stop = function(callback) { + // Stop the ping process + this.state = 'disconnected'; + // Call the callback + callback(null, null); +} + +PingStrategy.prototype.checkoutSecondary = function() { + // Get all secondary server keys + var keys = Object.keys(this.replicaset._state.secondaries); + // Contains the picked instance + var minimumPingMs = null; + var selectedInstance = null; + // Pick server key by the lowest ping time + for(var i = 0; i < keys.length; i++) { + // Fetch a server + var server = this.replicaset._state.secondaries[keys[i]]; + // If we don't have a ping time use it + if(server.runtimeStats['pingMs'] == null) { + // Set to 0 ms for the start + server.runtimeStats['pingMs'] = 0; + // Pick server + selectedInstance = server; + break; + } else { + // If the next server's ping time is less than the current one choose than one + if(minimumPingMs == null || server.runtimeStats['pingMs'] < minimumPingMs) { + minimumPingMs = server.runtimeStats['pingMs']; + selectedInstance = server; + } + } + } + + // Return the selected instance + return selectedInstance != null ? selectedInstance.checkoutReader() : null; +} + +PingStrategy.prototype._pingServer = function(callback) { + var self = this; + + // Ping server function + var pingFunction = function() { + if(self.state == 'disconnected') return; + var addresses = self.replicaset._state != null && self.replicaset._state.addresses != null ? self.replicaset._state.addresses : null; + // Grab all servers + var serverKeys = Object.keys(addresses); + // Number of server entries + var numberOfEntries = serverKeys.length; + // We got keys + for(var i = 0; i < serverKeys.length; i++) { + // We got a server instance + var server = addresses[serverKeys[i]]; + // Create a new server object, avoid using internal connections as they might + // be in an illegal state + new function(serverInstance) { + var server = new Server(serverInstance.host, serverInstance.port, {poolSize:1, timeout:500}); + var db = new self.Db(self.replicaset.db.databaseName, server); + // Add error listener + db.on("error", function(err) { + // Adjust the number of checks + numberOfEntries = numberOfEntries - 1; + // Close connection + db.close(); + // If we are done with all results coming back trigger ping again + if(numberOfEntries == 0 && self.state == 'connected') { + setTimeout(pingFunction, 1000); + } + }) + + // Open the db instance + db.open(function(err, p_db) { + if(err != null) { + db.close(); + } else { + // Startup time of the command + var startTime = new Date().getTime(); + // Execute ping on this connection + p_db.executeDbCommand({ping:1}, function(err, result) { + // Adjust the number of checks + numberOfEntries = numberOfEntries - 1; + // Get end time of the command + var endTime = new Date().getTime(); + // Store the ping time in the server instance state variable, if there is one + if(serverInstance != null && serverInstance.runtimeStats != null && serverInstance.isConnected()) { + serverInstance.runtimeStats['pingMs'] = (endTime - startTime); + } + + // Close server + p_db.close(); + // If we are done with all results coming back trigger ping again + if(numberOfEntries == 0 && self.state == 'connected') { + setTimeout(pingFunction, 1000); + } + }) + } + }) + }(server); + } + } + + // Start pingFunction + setTimeout(pingFunction, 1000); + // Do the callback + callback(null); +} diff --git a/node_modules/mongodb/lib/mongodb/connection/strategies/statistics_strategy.js b/node_modules/mongodb/lib/mongodb/connection/strategies/statistics_strategy.js new file mode 100644 index 0000000..0c8b1c0 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/connection/strategies/statistics_strategy.js @@ -0,0 +1,40 @@ +// The Statistics strategy uses the measure of each end-start time for each +// query executed against the db to calculate the mean, variance and standard deviation +// and pick the server which the lowest mean and deviation +var StatisticsStrategy = exports.StatisticsStrategy = function(replicaset) { + this.replicaset = replicaset; +} + +// Starts any needed code +StatisticsStrategy.prototype.start = function(callback) { + callback(null, null); +} + +StatisticsStrategy.prototype.stop = function(callback) { + // Remove reference to replicaset + this.replicaset = null; + // Perform callback + callback(null, null); +} + +StatisticsStrategy.prototype.checkoutSecondary = function() { + // Get all secondary server keys + var keys = Object.keys(this.replicaset._state.secondaries); + // Contains the picked instance + var minimumSscore = null; + var selectedInstance = null; + + // Pick server key by the lowest ping time + for(var i = 0; i < keys.length; i++) { + // Fetch a server + var server = this.replicaset._state.secondaries[keys[i]]; + // Pick server by lowest Sscore + if(minimumSscore == null || (server.queryStats.sScore < minimumSscore)) { + minimumSscore = server.queryStats.sScore; + selectedInstance = server; + } + } + + // Return the selected instance + return selectedInstance != null ? selectedInstance.checkoutReader() : null; +} diff --git a/node_modules/mongodb/lib/mongodb/cursor.js b/node_modules/mongodb/lib/mongodb/cursor.js new file mode 100644 index 0000000..28c9da4 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/cursor.js @@ -0,0 +1,702 @@ +var QueryCommand = require('./commands/query_command').QueryCommand, + GetMoreCommand = require('./commands/get_more_command').GetMoreCommand, + KillCursorCommand = require('./commands/kill_cursor_command').KillCursorCommand, + Long = require('bson').Long, + CursorStream = require('./cursorstream'), + utils = require('./utils'); + +/** + * Constructor for a cursor object that handles all the operations on query result + * using find. This cursor object is unidirectional and cannot traverse backwards. Clients should not be creating a cursor directly, + * but use find to acquire a cursor. + * + * @class Represents a Cursor. + * @param {Db} db the database object to work with. + * @param {Collection} collection the collection to query. + * @param {Object} selector the query selector. + * @param {Object} fields an object containing what fields to include or exclude from objects returned. + * @param {Number} skip number of documents to skip. + * @param {Number} limit the number of results to return. -1 has a special meaning and is used by Db.eval. A value of 1 will also be treated as if it were -1. + * @param {String|Array|Object} sort the required sorting for the query. + * @param {Object} hint force the query to use a specific index. + * @param {Boolean} explain return the explaination of the query. + * @param {Boolean} snapshot Snapshot mode assures no duplicates are returned. + * @param {Boolean} timeout allow the query to timeout. + * @param {Boolean} tailable allow the cursor to be tailable. + * @param {Number} batchSize the number of the subset of results to request the database to return for every request. This should initially be greater than 1 otherwise the database will automatically close the cursor. The batch size can be set to 1 with cursorInstance.batchSize after performing the initial query to the database. + * @param {Boolean} raw return all query documents as raw buffers (default false). + * @param {Boolean} read specify override of read from source (primary/secondary). + * @param {Boolean} returnKey only return the index key. + * @param {Number} maxScan limit the number of items to scan. + * @param {Number} min set index bounds. + * @param {Number} max set index bounds. + * @param {Boolean} showDiskLoc show disk location of results. + * @param {String} comment you can put a $comment field on a query to make looking in the profiler logs simpler. + */ +function Cursor(db, collection, selector, fields, skip, limit + , sort, hint, explain, snapshot, timeout, tailable, batchSize, slaveOk, raw, read + , returnKey, maxScan, min, max, showDiskLoc, comment) { + this.db = db; + this.collection = collection; + this.selector = selector; + this.fields = fields; + this.skipValue = skip == null ? 0 : skip; + this.limitValue = limit == null ? 0 : limit; + this.sortValue = sort; + this.hint = hint; + this.explainValue = explain; + this.snapshot = snapshot; + this.timeout = timeout == null ? true : timeout; + this.tailable = tailable; + this.batchSizeValue = batchSize == null ? 0 : batchSize; + this.slaveOk = slaveOk == null ? collection.slaveOk : slaveOk; + this.raw = raw == null ? false : raw; + this.read = read == null ? true : read; + this.returnKey = returnKey; + this.maxScan = maxScan; + this.min = min; + this.max = max; + this.showDiskLoc = showDiskLoc; + this.comment = comment; + + this.totalNumberOfRecords = 0; + this.items = []; + this.cursorId = Long.fromInt(0); + + // State variables for the cursor + this.state = Cursor.INIT; + // Keep track of the current query run + this.queryRun = false; + this.getMoreTimer = false; + this.collectionName = (this.db.databaseName ? this.db.databaseName + "." : '') + this.collection.collectionName; +}; + +/** + * Resets this cursor to its initial state. All settings like the query string, + * tailable, batchSizeValue, skipValue and limits are preserved. + * + * @return {Cursor} returns itself with rewind applied. + * @api public + */ +Cursor.prototype.rewind = function() { + var self = this; + + if (self.state != Cursor.INIT) { + if (self.state != Cursor.CLOSED) { + self.close(function() {}); + } + + self.numberOfReturned = 0; + self.totalNumberOfRecords = 0; + self.items = []; + self.cursorId = Long.fromInt(0); + self.state = Cursor.INIT; + self.queryRun = false; + } + + return self; +}; + + +/** + * Returns an array of documents. The caller is responsible for making sure that there + * is enough memory to store the results. Note that the array only contain partial + * results when this cursor had been previouly accessed. In that case, + * cursor.rewind() can be used to reset the cursor. + * + * @param {Function} callback This will be called after executing this method successfully. The first paramter will contain the Error object if an error occured, or null otherwise. The second paramter will contain an array of BSON deserialized objects as a result of the query. + * @return {null} + * @api public + */ +Cursor.prototype.toArray = function(callback) { + var self = this; + + if(!callback) { + throw new Error('callback is mandatory'); + } + + if(this.tailable) { + callback(new Error("Tailable cursor cannot be converted to array"), null); + } else if(this.state != Cursor.CLOSED) { + var items = []; + + this.each(function(err, item) { + if(err != null) return callback(err, null); + + if (item != null) { + items.push(item); + } else { + callback(err, items); + items = null; + self.items = []; + } + }); + } else { + callback(new Error("Cursor is closed"), null); + } +}; + +/** + * Iterates over all the documents for this cursor. As with **{cursor.toArray}**, + * not all of the elements will be iterated if this cursor had been previouly accessed. + * In that case, **{cursor.rewind}** can be used to reset the cursor. However, unlike + * **{cursor.toArray}**, the cursor will only hold a maximum of batch size elements + * at any given time if batch size is specified. Otherwise, the caller is responsible + * for making sure that the entire result can fit the memory. + * + * @param {Function} callback this will be called for while iterating every document of the query result. The first paramter will contain the Error object if an error occured, or null otherwise. While the second paramter will contain the document. + * @return {null} + * @api public + */ +Cursor.prototype.each = function(callback) { + var self = this; + + if (!callback) { + throw new Error('callback is mandatory'); + } + + if(this.state != Cursor.CLOSED) { + //FIX: stack overflow (on deep callback) (cred: https://github.com/limp/node-mongodb-native/commit/27da7e4b2af02035847f262b29837a94bbbf6ce2) + process.nextTick(function(){ + // Fetch the next object until there is no more objects + self.nextObject(function(err, item) { + if(err != null) return callback(err, null); + + if(item != null) { + callback(null, item); + self.each(callback); + } else { + // Close the cursor if done + self.state = Cursor.CLOSED; + callback(err, null); + } + + item = null; + }); + }); + } else { + callback(new Error("Cursor is closed"), null); + } +}; + +/** + * Determines how many result the query for this cursor will return + * + * @param {Function} callback this will be after executing this method. The first paramter will contain the Error object if an error occured, or null otherwise. While the second paramter will contain the number of results or null if an error occured. + * @return {null} + * @api public + */ +Cursor.prototype.count = function(callback) { + this.collection.count(this.selector, callback); +}; + +/** + * Sets the sort parameter of this cursor to the given value. + * + * This method has the following method signatures: + * (keyOrList, callback) + * (keyOrList, direction, callback) + * + * @param {String|Array|Object} keyOrList This can be a string or an array. If passed as a string, the string will be the field to sort. If passed an array, each element will represent a field to be sorted and should be an array that contains the format [string, direction]. + * @param {String|Number} direction this determines how the results are sorted. "asc", "ascending" or 1 for asceding order while "desc", "desceding or -1 for descending order. Note that the strings are case insensitive. + * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. + * @return {Cursor} an instance of this object. + * @api public + */ +Cursor.prototype.sort = function(keyOrList, direction, callback) { + callback = callback || function(){}; + if(typeof direction === "function") { callback = direction; direction = null; } + + if(this.tailable) { + callback(new Error("Tailable cursor doesn't support sorting"), null); + } else if(this.queryRun == true || this.state == Cursor.CLOSED) { + callback(new Error("Cursor is closed"), null); + } else { + var order = keyOrList; + + if(direction != null) { + order = [[keyOrList, direction]]; + } + + this.sortValue = order; + callback(null, this); + } + return this; +}; + +/** + * Sets the limit parameter of this cursor to the given value. + * + * @param {Number} limit the new limit. + * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object when the limit given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. + * @return {Cursor} an instance of this object. + * @api public + */ +Cursor.prototype.limit = function(limit, callback) { + callback = callback || function(){}; + + if(this.tailable) { + callback(new Error("Tailable cursor doesn't support limit"), null); + } else if(this.queryRun == true || this.state == Cursor.CLOSED) { + callback(new Error("Cursor is closed"), null); + } else { + if(limit != null && limit.constructor != Number) { + callback(new Error("limit requires an integer"), null); + } else { + this.limitValue = limit; + callback(null, this); + } + } + + return this; +}; + +/** + * Sets the skip parameter of this cursor to the given value. + * + * @param {Number} skip the new skip value. + * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object when the skip value given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. + * @return {Cursor} an instance of this object. + * @api public + */ +Cursor.prototype.skip = function(skip, callback) { + callback = callback || function(){}; + + if(this.tailable) { + callback(new Error("Tailable cursor doesn't support skip"), null); + } else if(this.queryRun == true || this.state == Cursor.CLOSED) { + callback(new Error("Cursor is closed"), null); + } else { + if(skip != null && skip.constructor != Number) { + callback(new Error("skip requires an integer"), null); + } else { + this.skipValue = skip; + callback(null, this); + } + } + + return this; +}; + +/** + * Sets the batch size parameter of this cursor to the given value. + * + * @param {Number} batchSize the new batch size. + * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object when the batchSize given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. + * @return {Cursor} an instance of this object. + * @api public + */ +Cursor.prototype.batchSize = function(batchSize, callback) { + if(this.state == Cursor.CLOSED) { + if(callback != null) { + return callback(new Error("Cursor is closed"), null); + } else { + throw new Error("Cursor is closed"); + } + } else if(batchSize != null && batchSize.constructor != Number) { + if(callback != null) { + return callback(new Error("batchSize requires an integer"), null); + } else { + throw new Error("batchSize requires an integer"); + } + } else { + this.batchSizeValue = batchSize; + if(callback != null) return callback(null, this); + } + + return this; +}; + +/** + * The limit used for the getMore command + * + * @return {Number} The number of records to request per batch. + * @ignore + * @api private + */ +var limitRequest = function(self) { + var requestedLimit = self.limitValue; + + if(self.limitValue > 0) { + if (self.batchSizeValue > 0) { + requestedLimit = self.limitValue < self.batchSizeValue ? + self.limitValue : self.batchSizeValue; + } + } else { + requestedLimit = self.batchSizeValue; + } + + return requestedLimit; +}; + + +/** + * Generates a QueryCommand object using the parameters of this cursor. + * + * @return {QueryCommand} The command object + * @ignore + * @api private + */ +var generateQueryCommand = function(self) { + // Unpack the options + var queryOptions = QueryCommand.OPTS_NONE; + if(!self.timeout) { + queryOptions |= QueryCommand.OPTS_NO_CURSOR_TIMEOUT; + } + + if(self.tailable != null) { + queryOptions |= QueryCommand.OPTS_TAILABLE_CURSOR; + self.skipValue = self.limitValue = 0; + } + + if(self.slaveOk) { + queryOptions |= QueryCommand.OPTS_SLAVE; + } + + // limitValue of -1 is a special case used by Db#eval + var numberToReturn = self.limitValue == -1 ? -1 : limitRequest(self); + + // Check if we need a special selector + if(self.sortValue != null || self.explainValue != null || self.hint != null || self.snapshot != null + || self.returnKey != null || self.maxScan != null || self.min != null || self.max != null + || self.showDiskLoc != null || self.comment != null) { + + // Build special selector + var specialSelector = {'query':self.selector}; + if(self.sortValue != null) specialSelector['orderby'] = utils.formattedOrderClause(self.sortValue); + if(self.hint != null && self.hint.constructor == Object) specialSelector['$hint'] = self.hint; + if(self.explainValue != null) specialSelector['$explain'] = true; + if(self.snapshot != null) specialSelector['$snapshot'] = true; + if(self.returnKey != null) specialSelector['$returnKey'] = self.returnKey; + if(self.maxScan != null) specialSelector['$maxScan'] = self.maxScan; + if(self.min != null) specialSelector['$min'] = self.min; + if(self.max != null) specialSelector['$max'] = self.max; + if(self.showDiskLoc != null) specialSelector['$showDiskLoc'] = self.showDiskLoc; + if(self.comment != null) specialSelector['$comment'] = self.comment; + + // Return the query + return new QueryCommand(self.db, self.collectionName, queryOptions, self.skipValue, numberToReturn, specialSelector, self.fields); + } else { + return new QueryCommand(self.db, self.collectionName, queryOptions, self.skipValue, numberToReturn, self.selector, self.fields); + } +}; + +/** + * @return {Object} Returns an object containing the sort value of this cursor with + * the proper formatting that can be used internally in this cursor. + * @ignore + * @api private + */ +Cursor.prototype.formattedOrderClause = function() { + return utils.formattedOrderClause(this.sortValue); +}; + +/** + * Converts the value of the sort direction into its equivalent numerical value. + * + * @param sortDirection {String|number} Range of acceptable values: + * 'ascending', 'descending', 'asc', 'desc', 1, -1 + * + * @return {number} The equivalent numerical value + * @throws Error if the given sortDirection is invalid + * @ignore + * @api private + */ +Cursor.prototype.formatSortValue = function(sortDirection) { + return utils.formatSortValue(sortDirection); +}; + +/** + * Gets the next document from the cursor. + * + * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object on error while the second parameter will contain a document from the returned result or null if there are no more results. + * @api public + */ +Cursor.prototype.nextObject = function(callback) { + var self = this; + + if(self.state == Cursor.INIT) { + var cmd; + try { + cmd = generateQueryCommand(self); + } catch (err) { + return callback(err, null); + } + + var commandHandler = function(err, result) { + if(err != null && result == null) return callback(err, null); + + if(!err && result.documents[0] && result.documents[0]['$err']) { + return self.close(function() {callback(result.documents[0]['$err'], null);}); + } + + self.queryRun = true; + self.state = Cursor.OPEN; // Adjust the state of the cursor + self.cursorId = result.cursorId; + self.totalNumberOfRecords = result.numberReturned; + + // Add the new documents to the list of items + self.items = self.items.concat(result.documents); + self.nextObject(callback); + result = null; + }; + + self.db._executeQueryCommand(cmd, {read:self.read, raw:self.raw}, commandHandler); + commandHandler = null; + } else if(self.items.length) { + callback(null, self.items.shift()); + } else if(self.cursorId.greaterThan(Long.fromInt(0))) { + getMore(self, callback); + } else { + // Force cursor to stay open + return self.close(function() {callback(null, null);}); + } +} + +/** + * Gets more results from the database if any. + * + * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object on error while the second parameter will contain a document from the returned result or null if there are no more results. + * @ignore + * @api private + */ +var getMore = function(self, callback) { + var limit = 0; + + if (!self.tailable && self.limitValue > 0) { + limit = self.limitValue - self.totalNumberOfRecords; + if (limit < 1) { + self.close(function() {callback(null, null);}); + return; + } + } + try { + var getMoreCommand = new GetMoreCommand(self.db, self.collectionName, limitRequest(self), self.cursorId); + // Execute the command + self.db._executeQueryCommand(getMoreCommand, {read:self.read, raw:self.raw}, function(err, result) { + try { + if(err != null) callback(err, null); + + self.cursorId = result.cursorId; + self.totalNumberOfRecords += result.numberReturned; + // Determine if there's more documents to fetch + if(result.numberReturned > 0) { + if (self.limitValue > 0) { + var excessResult = self.totalNumberOfRecords - self.limitValue; + + if (excessResult > 0) { + result.documents.splice(-1 * excessResult, excessResult); + } + } + + self.items = self.items.concat(result.documents); + callback(null, self.items.shift()); + } else if(self.tailable) { + self.getMoreTimer = setTimeout(function() {getMore(self, callback);}, 500); + } else { + self.close(function() {callback(null, null);}); + } + + result = null; + } catch(err) { + callback(err, null); + } + }); + + getMoreCommand = null; + } catch(err) { + var handleClose = function() { + callback(err, null); + }; + + self.close(handleClose); + handleClose = null; + } +} + +/** + * Gets a detailed information about how the query is performed on this cursor and how + * long it took the database to process it. + * + * @param {Function} callback this will be called after executing this method. The first parameter will always be null while the second parameter will be an object containing the details. + * @api public + */ +Cursor.prototype.explain = function(callback) { + var limit = (-1)*Math.abs(this.limitValue); + // Create a new cursor and fetch the plan + var cursor = new Cursor(this.db, this.collection, this.selector, this.fields, this.skipValue, limit, + this.sortValue, this.hint, true, this.snapshot, this.timeout, this.tailable, this.batchSizeValue); + // Fetch the explaination document + cursor.nextObject(function(err, item) { + if(err != null) return callback(err, null); + // close the cursor + cursor.close(function(err, result) { + if(err != null) return callback(err, null); + callback(null, item); + }); + }); +}; + +/** + * Returns a stream object that can be used to listen to and stream records + * (**Use the CursorStream object instead as this is deprected**) + * + * Options + * - **fetchSize** {Number} the number of records to fetch in each batch (steam specific batchSize). + * + * Events + * - **data** {function(item) {}} the data event triggers when a document is ready. + * - **error** {function(err) {}} the error event triggers if an error happens. + * - **end** {function() {}} the end event triggers when there is no more documents available. + * + * @param {Object} [options] additional options for streamRecords. + * @return {EventEmitter} returns a stream object. + * @api public + */ +Cursor.prototype.streamRecords = function(options) { + var args = Array.prototype.slice.call(arguments, 0); + options = args.length ? args.shift() : {}; + + var + self = this, + stream = new process.EventEmitter(), + recordLimitValue = this.limitValue || 0, + emittedRecordCount = 0, + queryCommand = generateQueryCommand(self); + + // see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol + queryCommand.numberToReturn = options.fetchSize ? options.fetchSize : 500; + // Execute the query + execute(queryCommand); + + function execute(command) { + self.db._executeQueryCommand(command, {read:self.read, raw:self.raw}, function(err,result) { + if(err) { + stream.emit('error', err); + self.close(function(){}); + return; + } + + if (!self.queryRun && result) { + self.queryRun = true; + self.cursorId = result.cursorId; + self.state = Cursor.OPEN; + self.getMoreCommand = new GetMoreCommand(self.db, self.collectionName, queryCommand.numberToReturn, result.cursorId); + } + + var resflagsMap = { + CursorNotFound:1<<0, + QueryFailure:1<<1, + ShardConfigStale:1<<2, + AwaitCapable:1<<3 + }; + + if(result.documents && result.documents.length && !(result.responseFlag & resflagsMap.QueryFailure)) { + try { + result.documents.forEach(function(doc){ + if(recordLimitValue && emittedRecordCount>=recordLimitValue) { + throw("done"); + } + emittedRecordCount++; + stream.emit('data', doc); + }); + } catch(err) { + if (err != "done") { throw err; } + else { + self.close(function(){ + stream.emit('end', recordLimitValue); + }); + self.close(function(){}); + return; + } + } + // rinse & repeat + execute(self.getMoreCommand); + } else { + self.close(function(){ + stream.emit('end', recordLimitValue); + }); + } + }); + } + + return stream; +}; + +/** + * Returns a Node ReadStream interface for this cursor. + * + * @return {CursorStream} returns a stream object. + * @api public + */ +Cursor.prototype.stream = function stream () { + return new CursorStream(this); +} + +/** + * Close the cursor. + * + * @param {Function} callback this will be called after executing this method. The first parameter will always contain null while the second parameter will contain a reference to this cursor. + * @return {null} + * @api public + */ +Cursor.prototype.close = function(callback) { + var self = this + this.getMoreTimer && clearTimeout(this.getMoreTimer); + // Close the cursor if not needed + if(this.cursorId instanceof Long && this.cursorId.greaterThan(Long.fromInt(0))) { + try { + var command = new KillCursorCommand(this.db, [this.cursorId]); + this.db._executeQueryCommand(command, {read:self.read, raw:self.raw}, null); + } catch(err) {} + } + + // Reset cursor id + this.cursorId = Long.fromInt(0); + // Set to closed status + this.state = Cursor.CLOSED; + + if(callback) { + callback(null, self); + self.items = []; + } + + return this; +}; + +/** + * Check if the cursor is closed or open. + * + * @return {Boolean} returns the state of the cursor. + * @api public + */ +Cursor.prototype.isClosed = function() { + return this.state == Cursor.CLOSED ? true : false; +}; + +/** + * Init state + * + * @classconstant INIT + **/ +Cursor.INIT = 0; + +/** + * Cursor open + * + * @classconstant OPEN + **/ +Cursor.OPEN = 1; + +/** + * Cursor closed + * + * @classconstant CLOSED + **/ +Cursor.CLOSED = 2; + +/** + * @ignore + * @api private + */ +exports.Cursor = Cursor; diff --git a/node_modules/mongodb/lib/mongodb/cursorstream.js b/node_modules/mongodb/lib/mongodb/cursorstream.js new file mode 100644 index 0000000..fd2ff65 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/cursorstream.js @@ -0,0 +1,141 @@ +/** + * Module dependecies. + */ +var Stream = require('stream').Stream; + +/** + * CursorStream + * + * Returns a stream interface for the **cursor**. + * + * Events + * - **data** {function(item) {}} the data event triggers when a document is ready. + * - **error** {function(err) {}} the error event triggers if an error happens. + * - **end** {function() {}} the end event triggers when there is no more documents available. + * + * @class Represents a CursorStream. + * @param {Cursor} cursor a cursor object that the stream wraps. + * @return {Stream} + */ +function CursorStream(cursor) { + if(!(this instanceof CursorStream)) return new CursorStream(cursor); + + Stream.call(this); + + this.readable = true; + this.paused = false; + this._cursor = cursor; + this._destroyed = null; + + // give time to hook up events + var self = this; + process.nextTick(function () { + self._init(); + }); +} + +/** + * Inherit from Stream + * @ignore + * @api private + */ +CursorStream.prototype.__proto__ = Stream.prototype; + +/** + * Flag stating whether or not this stream is readable. + */ +CursorStream.prototype.readable; + +/** + * Flag stating whether or not this stream is paused. + */ +CursorStream.prototype.paused; + +/** + * Initialize the cursor. + * @ignore + * @api private + */ +CursorStream.prototype._init = function () { + if (this._destroyed) return; + this._next(); +} + +/** + * Pull the next document from the cursor. + * @ignore + * @api private + */ +CursorStream.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. + process.nextTick(function () { + self._cursor.nextObject(function (err, doc) { + self._onNextObject(err, doc); + }); + }); +} + +/** + * Handle each document as its returned from the cursor. + * @ignore + * @api private + */ +CursorStream.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(); + + this.emit('data', doc); + this._next(); +} + +/** + * Pauses the stream. + * + * @api public + */ +CursorStream.prototype.pause = function () { + this.paused = true; +} + +/** + * Resumes the stream. + * + * @api public + */ +CursorStream.prototype.resume = function () { + this.paused = false; + this._next(); +} + +/** + * Destroys the stream, closing the underlying + * cursor. No more events will be emitted. + * + * @api public + */ +CursorStream.prototype.destroy = function (err) { + if (this._destroyed) return; + this._destroyed = true; + this.readable = false; + + this._cursor.close(); + + if (err) { + this.emit('error', err); + } + + this.emit('close'); +} + +// TODO - maybe implement the raw option to pass binary? +//CursorStream.prototype.setEncoding = function () { +//} + +module.exports = exports = CursorStream; diff --git a/node_modules/mongodb/lib/mongodb/db.js b/node_modules/mongodb/lib/mongodb/db.js new file mode 100644 index 0000000..b892fbd --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/db.js @@ -0,0 +1,1788 @@ +/** + * Module dependencies. + * @ignore + */ +var QueryCommand = require('./commands/query_command').QueryCommand, + DbCommand = require('./commands/db_command').DbCommand, + MongoReply = require('./responses/mongo_reply').MongoReply, + Admin = require('./admin').Admin, + Collection = require('./collection').Collection, + Server = require('./connection/server').Server, + ReplSetServers = require('./connection/repl_set_servers').ReplSetServers, + Cursor = require('./cursor').Cursor, + EventEmitter = require('events').EventEmitter, + inherits = require('util').inherits, + crypto = require('crypto'); + +/** + * Internal class for callback storage + * @ignore + */ +var CallbackStore = function() { + // Make class an event emitter + EventEmitter.call(this); + // Add a info about call variable + this._notReplied = {}; +} + +/** + * @ignore + */ +inherits(CallbackStore, EventEmitter); + +/** + * Create a new Db instance. + * + * Options + * - **strict** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, execute insert with a getLastError command returning the result of the insert command. + * - **native_parser** {Boolean, default:false}, use c++ bson parser. + * - **forceServerObjectId** {Boolean, default:false}, force server to create _id fields instead of client. + * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. + * - **slaveOk** {Boolean, default:false}, allow reads from secondaries. + * - **serializeFunctions** {Boolean, default:false}, serialize functions. + * - **raw** {Boolean, default:false}, peform operations using raw bson buffers. + * - **recordQueryStats** {Boolean, default:false}, record query statistics during execution. + * - **reaper** {Boolean, default:false}, enables the reaper, timing out calls that never return. + * - **reaperInterval** {Number, default:10000}, number of miliseconds between reaper wakups. + * - **reaperTimeout** {Number, default:30000}, the amount of time before a callback times out. + * - **retryMiliSeconds** {Number, default:5000}, number of miliseconds between retries. + * - **numberOfRetries** {Number, default:5}, number of retries off connection. + * + * @class Represents a Collection + * @param {String} databaseName name of the database. + * @param {Object} serverConfig server config object. + * @param {Object} [options] additional options for the collection. + */ +function Db(databaseName, serverConfig, options) { + + if(!(this instanceof Db)) return new Db(databaseName, serverConfig, options); + + EventEmitter.call(this); + this.databaseName = databaseName; + this.serverConfig = serverConfig; + this.options = options == null ? {} : options; + // State to check against if the user force closed db + this._applicationClosed = false; + // Fetch the override flag if any + var overrideUsedFlag = this.options['override_used_flag'] == null ? false : this.options['override_used_flag']; + // Verify that nobody is using this config + if(!overrideUsedFlag && typeof this.serverConfig == 'object' && this.serverConfig._isUsed()) { + throw new Error("A Server or ReplSetServers instance cannot be shared across multiple Db instances"); + } else if(!overrideUsedFlag && typeof this.serverConfig == 'object'){ + // Set being used + this.serverConfig._used = true; + } + + // Ensure we have a valid db name + validateDatabaseName(databaseName); + + // Contains all the connections for the db + try { + this.native_parser = this.options.native_parser; + // The bson lib + var bsonLib = this.bsonLib = this.options.native_parser ? require('bson').BSONNative : new require('bson').BSONPure; + // Fetch the serializer object + var BSON = bsonLib.BSON; + // Create a new instance + this.bson = new BSON([bsonLib.Long, bsonLib.ObjectID, bsonLib.Binary, bsonLib.Code, bsonLib.DBRef, bsonLib.Symbol, bsonLib.Double, bsonLib.Timestamp, bsonLib.MaxKey, bsonLib.MinKey]); + // Backward compatibility to access types + this.bson_deserializer = bsonLib; + this.bson_serializer = bsonLib; + } catch (err) { + // If we tried to instantiate the native driver + throw "Native bson parser not compiled, please compile or avoid using native_parser=true"; + } + + // Internal state of the server + this._state = 'disconnected'; + + this.pkFactory = this.options.pk == null ? bsonLib.ObjectID : this.options.pk; + this.forceServerObjectId = this.options.forceServerObjectId != null ? this.options.forceServerObjectId : false; + // Added strict + this.strict = this.options.strict == null ? false : this.options.strict; + this.notReplied ={}; + this.isInitializing = true; + this.auths = []; + this.openCalled = false; + + // Command queue, keeps a list of incoming commands that need to be executed once the connection is up + this.commands = []; + + // Contains all the callbacks + this._callBackStore = new CallbackStore(); + + // Set up logger + this.logger = this.options.logger != null + && (typeof this.options.logger.debug == 'function') + && (typeof this.options.logger.error == 'function') + && (typeof this.options.logger.log == 'function') + ? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; + // Allow slaveOk + this.slaveOk = this.options["slave_ok"] == null ? false : this.options["slave_ok"]; + + var self = this; + // Associate the logger with the server config + this.serverConfig.logger = this.logger; + this.tag = new Date().getTime(); + // Just keeps list of events we allow + this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[]}; + + // Controls serialization options + this.serializeFunctions = this.options.serializeFunctions != null ? this.options.serializeFunctions : false; + + // Raw mode + this.raw = this.options.raw != null ? this.options.raw : false; + + // Record query stats + this.recordQueryStats = this.options.recordQueryStats != null ? this.options.recordQueryStats : false; + + // If we have server stats let's make sure the driver objects have it enabled + if(this.recordQueryStats == true) { + this.serverConfig.enableRecordQueryStats(true); + } + + // Reaper enable setting + this.reaperEnabled = this.options.reaper != null ? this.options.reaper : false; + this._lastReaperTimestamp = new Date().getTime(); + + // Retry information + this.retryMiliSeconds = this.options.retryMiliSeconds != null ? this.options.retryMiliSeconds : 5000; + this.numberOfRetries = this.options.numberOfRetries != null ? this.options.numberOfRetries : 5; + + // Reaper information + this.reaperInterval = this.options.reaperInterval != null ? this.options.reaperInterval : 10000; + this.reaperTimeout = this.options.reaperTimeout != null ? this.options.reaperTimeout : 30000; + + // get self + var self = this; + // State of the db connection + Object.defineProperty(this, "state", { enumerable: true + , get: function () { + return this.serverConfig._serverState; + } + }); +}; + +/** + * The reaper cleans up any callbacks that have not returned inside the space set by + * the parameter reaperTimeout, it will only attempt to reap if the time since last reap + * is bigger or equal to the reaperInterval value + * @ignore + */ +var reaper = function(dbInstance, reaperInterval, reaperTimeout) { + // Get current time, compare to reaper interval + var currentTime = new Date().getTime(); + // Now calculate current time difference to check if it's time to reap + if((currentTime - dbInstance._lastReaperTimestamp) >= reaperInterval) { + // Save current timestamp for next reaper iteration + dbInstance._lastReaperTimestamp = currentTime; + // Get all non-replied to messages + var keys = Object.keys(dbInstance._callBackStore._notReplied); + // Iterate over all callbacks + for(var i = 0; i < keys.length; i++) { + // Fetch the current key + var key = keys[i]; + // Get info element + var info = dbInstance._callBackStore._notReplied[key]; + // If it's timed out let's remove the callback and return an error + if((currentTime - info.start) > reaperTimeout) { + // Cleanup + delete dbInstance._callBackStore._notReplied[key]; + // Perform callback in next Tick + process.nextTick(function() { + dbInstance._callBackStore.emit(key, new Error("operation timed out"), null); + }); + } + } + // Return reaping was done + return true; + } else { + // No reaping done + return false; + } +} + +/** + * @ignore + */ +function validateDatabaseName(databaseName) { + if(typeof databaseName !== 'string') throw new Error("database name must be a string"); + if(databaseName.length === 0) throw new Error("database name cannot be the empty string"); + + var invalidChars = [" ", ".", "$", "/", "\\"]; + for(var i = 0; i < invalidChars.length; i++) { + if(databaseName.indexOf(invalidChars[i]) != -1) throw new Error("database names cannot contain the character '" + invalidChars[i] + "'"); + } +} + +/** + * @ignore + */ +inherits(Db, EventEmitter); + +/** + * Initialize the database connection. + * + * @param {Function} callback returns index information. + * @return {null} + * @api public + */ +Db.prototype.open = function(callback) { + var self = this; + + // Check that the user has not called this twice + if(this.openCalled) { + // Close db + this.close(); + // Throw error + throw new Error("db object already connecting, open cannot be called multiple times"); + } + + // Set that db has been opened + this.openCalled = true; + + // Set the status of the server + self._state = 'connecting'; + // Set up connections + if(self.serverConfig instanceof Server || self.serverConfig instanceof ReplSetServers) { + self.serverConfig.connect(self, {firstCall: true}, function(err, result) { + if(err != null) { + // Return error from connection + return callback(err, null); + } + // Set the status of the server + self._state = 'connected'; + // Callback + return callback(null, self); + }); + } else { + return callback(Error("Server parameter must be of type Server or ReplSetServers"), null); + } +}; + +/** + * Create a new Db instance sharing the current socket connections. + * + * @param {String} dbName the name of the database we want to use. + * @return {Db} a db instance using the new database. + * @api public + */ +Db.prototype.db = function(dbName) { + // Copy the options and add out internal override of the not shared flag + var options = {}; + for(var key in this.options) { + options[key] = this.options[key]; + } + // Add override flag + options['override_used_flag'] = true; + // Create a new db instance + var newDbInstance = new Db(dbName, this.serverConfig, options); + // Add the instance to the list of approved db instances + var allServerInstances = this.serverConfig.allServerInstances(); + // Add ourselves to all server callback instances + for(var i = 0; i < allServerInstances.length; i++) { + var server = allServerInstances[i]; + server.dbInstances.push(newDbInstance); + } + // Return new db object + return newDbInstance; +} + +/** + * Close the current db connection, including all the child db instances. Emits close event if no callback is provided. + * + * @param {Boolean} [forceClose] connection can never be reused. + * @param {Function} [callback] returns the results. + * @return {null} + * @api public + */ +Db.prototype.close = function(forceClose, callback) { + // Unpack calls + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + // Ensure we force close all connections + this._applicationClosed = args.length ? args.shift() : false; + // Remove all listeners and close the connection + this.serverConfig.close(callback); + // Emit the close event + if(typeof callback !== 'function') this.emit("close"); + + // Emit close event across all db instances sharing the sockets + var allServerInstances = this.serverConfig.allServerInstances(); + // Fetch the first server instance + if(Array.isArray(allServerInstances) && allServerInstances.length > 0) { + var server = allServerInstances[0]; + // For all db instances signal all db instances + if(Array.isArray(server.dbInstances) && server.dbInstances.length > 1) { + for(var i = 0; i < server.dbInstances.length; i++) { + var dbInstance = server.dbInstances[i]; + // Check if it's our current db instance and skip if it is + if(dbInstance.databaseName !== this.databaseName && dbInstance.tag !== this.tag) { + server.dbInstances[i].emit("close"); + } + } + } + } + + // Remove all listeners + this.removeAllEventListeners(); + // You can reuse the db as everything is shut down + this.openCalled = false; +}; + +/** + * Access the Admin database + * + * @param {Function} [callback] returns the results. + * @return {Admin} the admin db object. + * @api public + */ +Db.prototype.admin = function(callback) { + if(callback == null) return new Admin(this); + callback(null, new Admin(this)); +}; + +/** + * Returns a cursor to all the collection information. + * + * @param {String} [collectionName] the collection name we wish to retrieve the information from. + * @param {Function} callback returns option results. + * @return {null} + * @api public + */ +Db.prototype.collectionsInfo = function(collectionName, callback) { + if(callback == null && typeof collectionName == 'function') { callback = collectionName; collectionName = null; } + // Create selector + var selector = {}; + // If we are limiting the access to a specific collection name + if(collectionName != null) selector.name = this.databaseName + "." + collectionName; + + // Return Cursor + // callback for backward compatibility + if(callback) { + callback(null, new Cursor(this, new Collection(this, DbCommand.SYSTEM_NAMESPACE_COLLECTION), selector)); + } else { + return new Cursor(this, new Collection(this, DbCommand.SYSTEM_NAMESPACE_COLLECTION), selector); + } +}; + +/** + * Get the list of all collection names for the specified db + * + * @param {String} [collectionName] the collection name we wish to filter by. + * @param {Function} callback returns option results. + * @return {null} + * @api public + */ +Db.prototype.collectionNames = function(collectionName, callback) { + if(callback == null && typeof collectionName == 'function') { callback = collectionName; collectionName = null; } + var self = this; + // Let's make our own callback to reuse the existing collections info method + self.collectionsInfo(collectionName, function(err, cursor) { + if(err != null) return callback(err, null); + + cursor.toArray(function(err, documents) { + if(err != null) return callback(err, null); + + // List of result documents that have been filtered + var filtered_documents = []; + // Remove any collections that are not part of the db or a system db signed with $ + documents.forEach(function(document) { + if(!(document.name.indexOf(self.databaseName) == -1 || document.name.indexOf('$') != -1)) + filtered_documents.push(document); + }); + // Return filtered items + callback(null, filtered_documents); + }); + }); +}; + +/** + * Fetch a specific collection (containing the actual collection information) + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * - **slaveOk** {Boolean, default:false}, Allow reads from secondaries. + * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. + * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. + * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. + * + * @param {String} collectionName the collection name we wish to access. + * @param {Object} [options] returns option results. + * @param {Function} [callback] returns the results. + * @return {null} + * @api public + */ +Db.prototype.collection = function(collectionName, options, callback) { + var self = this; + if(typeof options === "function") { callback = options; options = {}; } + // Execute safe + if(options && options.safe || this.strict) { + self.collectionNames(collectionName, function(err, collections) { + if(err != null) return callback(err, null); + + if(collections.length == 0) { + return callback(new Error("Collection " + collectionName + " does not exist. Currently in strict mode."), null); + } else { + try { + var collection = new Collection(self, collectionName, self.pkFactory, options); + } catch(err) { + return callback(err, null); + } + return callback(null, collection); + } + }); + } else { + try { + var collection = new Collection(self, collectionName, self.pkFactory, options); + } catch(err) { + if(callback == null) { + throw err; + } else { + return callback(err, null); + } + } + + // If we have no callback return collection object + return callback == null ? collection : callback(null, collection); + } +}; + +/** + * Fetch all collections for the current db. + * + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.collections = function(callback) { + var self = this; + // Let's get the collection names + self.collectionNames(function(err, documents) { + if(err != null) return callback(err, null); + var collections = []; + documents.forEach(function(document) { + collections.push(new Collection(self, document.name.replace(self.databaseName + ".", ''), self.pkFactory)); + }); + // Return the collection objects + callback(null, collections); + }); +}; + +/** + * Evaluate javascript on the server + * + * Options + * - **nolock** {Boolean, default:false}, Tell MongoDB not to block on the evaulation of the javascript. + * + * @param {Code} code javascript to execute on server. + * @param {Object|Array} [parameters] the parameters for the call. + * @param {Object} [options] the options + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.eval = function(code, parameters, options, callback) { + // Unpack calls + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + parameters = args.length ? args.shift() : parameters; + options = args.length ? args.shift() : {}; + + var finalCode = code; + var finalParameters = []; + // If not a code object translate to one + if(!(finalCode instanceof this.bsonLib.Code)) { + finalCode = new this.bsonLib.Code(finalCode); + } + + // Ensure the parameters are correct + if(parameters != null && parameters.constructor != Array && typeof parameters !== 'function') { + finalParameters = [parameters]; + } else if(parameters != null && parameters.constructor == Array && typeof parameters !== 'function') { + finalParameters = parameters; + } + // Create execution selector + var selector = {'$eval':finalCode, 'args':finalParameters}; + // Check if the nolock parameter is passed in + if(options['nolock']) { + selector['nolock'] = options['nolock']; + } + + // Iterate through all the fields of the index + new Cursor(this, new Collection(this, DbCommand.SYSTEM_COMMAND_COLLECTION), selector, options, 0, -1).nextObject(function(err, result) { + if(err != null) return callback(err, null); + + if(result.ok == 1) { + callback(null, result.retval); + } else { + callback(new Error("eval failed: " + result.errmsg), null); return; + } + }); +}; + +/** + * Dereference a dbref, against a db + * + * @param {DBRef} dbRef db reference object we wish to resolve. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.dereference = function(dbRef, callback) { + var db = this; + // If we have a db reference then let's get the db first + if(dbRef.db != null) db = this.db(dbRef.db); + // Fetch the collection and find the reference + db.collection(dbRef.namespace, function(err, collection) { + if(err != null) return callback(err, null); + + collection.findOne({'_id':dbRef.oid}, function(err, result) { + callback(err, result); + }); + }); +}; + +/** + * Logout user from server, fire off on all connections and remove all auth info + * + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.logout = function(callback) { + var self = this; + // Let's generate the logout command object + var logoutCommand = DbCommand.logoutCommand(self, {logout:1}); + self._executeQueryCommand(logoutCommand, {onAll:true}, function(err, result) { + // Reset auth + self.auths = []; + // Handle any errors + if(err == null && result.documents[0].ok == 1) { + callback(null, true); + } else { + err != null ? callback(err, false) : callback(new Error(result.documents[0].errmsg), false); + } + }); +} + +/** + * Authenticate a user against the server. + * + * @param {String} username username. + * @param {String} password password. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.authenticate = function(username, password, callback) { + var self = this; + + // Push the new auth if we have no previous record + self.auths = [{'username':username, 'password':password}]; + // Get the amount of connections in the pool to ensure we have authenticated all comments + var numberOfConnections = this.serverConfig.allRawConnections().length; + var errorObject = null; + + // Execute all four + this._executeQueryCommand(DbCommand.createGetNonceCommand(self), {onAll:true}, function(err, result, connection) { + // Execute on all the connections + if(err == null) { + // Nonce used to make authentication request with md5 hash + var nonce = result.documents[0].nonce; + // Execute command + self._executeQueryCommand(DbCommand.createAuthenticationCommand(self, username, password, nonce), {connection:connection}, function(err, result) { + // Ensure we save any error + if(err) { + errorObject = err; + } else if(result.documents[0].err != null || result.documents[0].errmsg != null){ + errorObject = self.wrap(result.documents[0]); + } + + // Count down + numberOfConnections = numberOfConnections - 1; + + // If we are done with the callbacks return + if(numberOfConnections <= 0) { + if(errorObject == null && result.documents[0].ok == 1) { + callback(errorObject, true); + } else { + callback(errorObject, false); + } + } + }); + } + }); +}; + +/** + * Add a user to the database. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * + * @param {String} username username. + * @param {String} password password. + * @param {Object} [options] additional options during update. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.addUser = function(username, password, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + // Figure out the safe mode settings + var safe = self.strict != null && self.strict == false ? true : self.strict; + // Override with options passed in if applicable + safe = options != null && options['safe'] != null ? options['safe'] : safe; + // Ensure it's at least set to safe + safe = safe == null ? true : safe; + + // Use node md5 generator + var md5 = crypto.createHash('md5'); + // Generate keys used for authentication + md5.update(username + ":mongo:" + password); + var userPassword = md5.digest('hex'); + // Fetch a user collection + this.collection(DbCommand.SYSTEM_USER_COLLECTION, function(err, collection) { + collection.find({user: username}).toArray(function(err, documents) { + // We got an error (f.ex not authorized) + if(err != null) return callback(err, null); + // We have a user, let's update the password + if(documents.length > 0) { + collection.update({user: username},{user: username, pwd: userPassword}, {safe:safe}, function(err, results) { + callback(err, documents); + }); + } else { + collection.insert({user: username, pwd: userPassword}, {safe:safe}, function(err, documents) { + callback(err, documents); + }); + } + }); + }); +}; + +/** + * Remove a user from a database + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * + * @param {String} username username. + * @param {Object} [options] additional options during update. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.removeUser = function(username, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + // Figure out the safe mode settings + var safe = self.strict != null && self.strict == false ? true : self.strict; + // Override with options passed in if applicable + safe = options != null && options['safe'] != null ? options['safe'] : safe; + // Ensure it's at least set to safe + safe = safe == null ? true : safe; + + // Fetch a user collection + this.collection(DbCommand.SYSTEM_USER_COLLECTION, function(err, collection) { + collection.findOne({user: username}, function(err, user) { + if(user != null) { + collection.remove({user: username}, {safe:safe}, function(err, result) { + callback(err, true); + }); + } else { + callback(err, false); + } + }); + }); +}; + +/** + * Creates a collection on a server pre-allocating space, need to create f.ex capped collections. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * - **slaveOk** {Boolean, default:false}, Allow reads from secondaries. + * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. + * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. + * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. + * - **capped** {Boolean, default:false}, create a capped collection. + * - **size** {Number}, the size of the capped collection in bytes. + * - **max** {Number}, the maximum number of documents in the capped collection. + * - **autoIndexId** {Boolean, default:false}, create an index on the _id field of the document, not created automatically on capped collections. + * + * @param {String} collectionName the collection name we wish to access. + * @param {Object} [options] returns option results. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.createCollection = function(collectionName, options, callback) { + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : null; + var self = this; + + // Figure out the safe mode settings + var safe = self.strict != null && self.strict == false ? true : self.strict; + // Override with options passed in if applicable + safe = options != null && options['safe'] != null ? options['safe'] : safe; + // Ensure it's at least set to safe + safe = safe == null ? true : safe; + + // Check if we have the name + this.collectionNames(collectionName, function(err, collections) { + if(err != null) return callback(err, null); + + var found = false; + collections.forEach(function(collection) { + if(collection.name == self.databaseName + "." + collectionName) found = true; + }); + + // If the collection exists either throw an exception (if db in strict mode) or return the existing collection + if(found && ((options && options.safe) || self.strict)) { + return callback(new Error("Collection " + collectionName + " already exists. Currently in strict mode."), null); + } else if(found){ + try { + var collection = new Collection(self, collectionName, self.pkFactory, options); + } catch(err) { + return callback(err, null); + } + return callback(null, collection); + } + + // Create a new collection and return it + self._executeQueryCommand(DbCommand.createCreateCollectionCommand(self, collectionName, options), {read:false, safe:safe}, function(err, result) { + var document = result.documents[0]; + // If we have no error let's return the collection + if(err == null && document.ok == 1) { + try { + var collection = new Collection(self, collectionName, self.pkFactory, options); + } catch(err) { + return callback(err, null); + } + return callback(null, collection); + } else { + err != null ? callback(err, null) : callback(self.wrap(document), null); + } + }); + }); +}; + +/** + * Execute a command hash against MongoDB. This lets you acess any commands not available through the api on the server. + * + * @param {Object} selector the command hash to send to the server, ex: {ping:1}. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.command = function(selector, callback) { + var cursor = new Cursor(this, new Collection(this, DbCommand.SYSTEM_COMMAND_COLLECTION), selector, {}, 0, -1, null, null, null, null, QueryCommand.OPTS_NO_CURSOR_TIMEOUT); + cursor.nextObject(callback); +}; + +/** + * Drop a collection from the database, removing it permanently. New accesses will create a new collection. + * + * @param {String} collectionName the name of the collection we wish to drop. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.dropCollection = function(collectionName, callback) { + var self = this; + + // Drop the collection + this._executeQueryCommand(DbCommand.createDropCollectionCommand(this, collectionName), function(err, result) { + if(err == null && result.documents[0].ok == 1) { + if(callback != null) return callback(null, true); + } else { + if(callback != null) err != null ? callback(err, null) : callback(self.wrap(result.documents[0]), null); + } + }); +}; + +/** + * Rename a collection. + * + * @param {String} fromCollection the name of the current collection we wish to rename. + * @param {String} toCollection the new name of the collection. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.renameCollection = function(fromCollection, toCollection, callback) { + var self = this; + + // Execute the command, return the new renamed collection if successful + this._executeQueryCommand(DbCommand.createRenameCollectionCommand(this, fromCollection, toCollection), function(err, result) { + if(err == null && result.documents[0].ok == 1) { + if(callback != null) return callback(null, new Collection(self, toCollection, self.pkFactory)); + } else { + if(callback != null) err != null ? callback(err, null) : callback(self.wrap(result.documents[0]), null); + } + }); +}; + +/** + * Return last error message for the given connection, note options can be combined. + * + * Options + * - **fsync** {Boolean, default:false}, option forces the database to fsync all files before returning. + * - **j** {Boolean, default:false}, awaits the journal commit before returning, > MongoDB 2.0. + * - **w** {Number}, until a write operation has been replicated to N servers. + * - **wtimeout** {Number}, number of miliseconds to wait before timing out. + * + * Connection Options + * - **connection** {Connection}, fire the getLastError down a specific connection. + * + * @param {Object} [options] returns option results. + * @param {Object} [connectionOptions] returns option results. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.lastError = function(options, connectionOptions, callback) { + // Unpack calls + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + options = args.length ? args.shift() : {}; + connectionOptions = args.length ? args.shift() : {}; + + this._executeQueryCommand(DbCommand.createGetLastErrorCommand(options, this), connectionOptions, function(err, error) { + callback(err, error && error.documents); + }); +}; + +/** + * Legacy method calls. + * + * @ignore + * @api private + */ +Db.prototype.error = Db.prototype.lastError; +Db.prototype.lastStatus = Db.prototype.lastError; + +/** + * Return all errors up to the last time db reset_error_history was called. + * + * Options + * - **connection** {Connection}, fire the getLastError down a specific connection. + * + * @param {Object} [options] returns option results. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.previousErrors = function(options, callback) { + // Unpack calls + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + this._executeQueryCommand(DbCommand.createGetPreviousErrorsCommand(this), options, function(err, error) { + callback(err, error.documents); + }); +}; + +/** + * Runs a command on the database. + * @ignore + * @api private + */ +Db.prototype.executeDbCommand = function(command_hash, options, callback) { + if(callback == null) { callback = options; options = {}; } + this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, command_hash, options), options, callback); +}; + +/** + * Runs a command on the database as admin. + * @ignore + * @api private + */ +Db.prototype.executeDbAdminCommand = function(command_hash, options, callback) { + if(callback == null) { callback = options; options = {}; } + this._executeQueryCommand(DbCommand.createAdminDbCommand(this, command_hash, options), callback); +}; + +/** + * Resets the error history of the mongo instance. + * + * Options + * - **connection** {Connection}, fire the getLastError down a specific connection. + * + * @param {Object} [options] returns option results. + * @param {Function} callback returns the results. + * @return {null} + * @api public + */ +Db.prototype.resetErrorHistory = function(options, callback) { + // Unpack calls + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + this._executeQueryCommand(DbCommand.createResetErrorHistoryCommand(this), options, function(err, error) { + callback(err, error.documents); + }); +}; + +/** + * Creates an index on the collection. + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. + * - **unique** {Boolean, default:false}, creates an unique index. + * - **sparse** {Boolean, default:false}, creates a sparse index. + * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. + * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value + * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. + * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. + * + * @param {String} collectionName name of the collection to create the index on. + * @param {Object} fieldOrSpec fieldOrSpec that defines the index. + * @param {Object} [options] additional options during update. + * @param {Function} callback for results. + * @return {null} + * @api public + */ +Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + options = args.length ? args.shift() : {}; + options = typeof callback === 'function' ? options : callback; + options = options == null ? {} : options; + + // Collect errorOptions + var errorOptions = options.safe != null ? options.safe : null; + errorOptions = errorOptions == null && self.strict != null ? self.strict : errorOptions; + + // If we have a write concern set and no callback throw error + if(errorOptions != null && errorOptions != false && (typeof callback !== 'function' && typeof options !== 'function')) throw new Error("safe cannot be used without a callback"); + + // Create command + var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); + // Default command options + var commandOptions = {}; + + // If we have error conditions set handle them + if(errorOptions && errorOptions != false) { + // Insert options + commandOptions['read'] = false; + // If we have safe set set async to false + if(errorOptions == null) commandOptions['async'] = true; + + // Set safe option + commandOptions['safe'] = errorOptions; + // If we have an error option + if(typeof errorOptions == 'object') { + var keys = Object.keys(errorOptions); + for(var i = 0; i < keys.length; i++) { + commandOptions[keys[i]] = errorOptions[keys[i]]; + } + } + + // Execute insert command + this._executeInsertCommand(command, commandOptions, function(err, result) { + if(err != null) return callback(err, null); + + result = result && result.documents; + if (result[0].err) { + callback(self.wrap(result[0])); + } else { + callback(null, command.documents[0].name); + } + }); + } else { + // Execute insert command + var result = this._executeInsertCommand(command, commandOptions); + // If no callback just return + if(!callback) return; + // If error return error + if(result instanceof Error) { + return callback(result); + } + // Otherwise just return + return callback(null, null); + } +}; + +/** + * Ensures that an index exists, if it does not it creates it + * + * Options + * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a + * - **unique** {Boolean, default:false}, creates an unique index. + * - **sparse** {Boolean, default:false}, creates a sparse index. + * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. + * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value + * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. + * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. + * - **v** {Number}, specify the format version of the indexes. + * + * @param {String} collectionName name of the collection to create the index on. + * @param {Object} fieldOrSpec fieldOrSpec that defines the index. + * @param {Object} [options] additional options during update. + * @param {Function} callback for results. + * @return {null} + * @api public + */ +Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + options = args.length ? args.shift() : {}; + options = typeof callback === 'function' ? options : callback; + options = options == null ? {} : options; + + // Collect errorOptions + var errorOptions = options.safe != null ? options.safe : null; + errorOptions = errorOptions == null && self.strict != null ? self.strict : errorOptions; + + // If we have a write concern set and no callback throw error + if(errorOptions != null && errorOptions != false && (typeof callback !== 'function' && typeof options !== 'function')) throw new Error("safe cannot be used without a callback"); + + // Create command + var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); + var index_name = command.documents[0].name; + + // Default command options + var commandOptions = {}; + // Check if the index allready exists + this.indexInformation(collectionName, function(err, collectionInfo) { + if(err != null) return callback(err, null); + + if(!collectionInfo[index_name]) { + // If we have error conditions set handle them + if(errorOptions && errorOptions != false) { + // Insert options + commandOptions['read'] = false; + // If we have safe set set async to false + if(errorOptions == null) commandOptions['async'] = true; + + // Set safe option + commandOptions['safe'] = errorOptions; + // If we have an error option + if(typeof errorOptions == 'object') { + var keys = Object.keys(errorOptions); + for(var i = 0; i < keys.length; i++) { + commandOptions[keys[i]] = errorOptions[keys[i]]; + } + } + + self._executeInsertCommand(command, commandOptions, function(err, result) { + // Only callback if we have one specified + if(typeof callback === 'function') { + if(err != null) return callback(err, null); + + result = result && result.documents; + if (result[0].err) { + callback(self.wrap(result[0])); + } else { + callback(null, command.documents[0].name); + } + } + }); + } else { + // Execute insert command + var result = self._executeInsertCommand(command, commandOptions); + // If no callback just return + if(!callback) return; + // If error return error + if(result instanceof Error) { + return callback(result); + } + // Otherwise just return + return callback(null, index_name); + } + } else { + if(typeof callback === 'function') return callback(null, index_name); + } + }); +}; + +/** + * Returns the information available on allocated cursors. + * + * @param {Function} callback for results. + * @return {null} + * @api public + */ +Db.prototype.cursorInfo = function(callback) { + this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, {'cursorInfo':1}), function(err, result) { + callback(err, result.documents[0]); + }); +}; + +/** + * Drop an index on a collection. + * + * @param {String} collectionName the name of the collection where the command will drop an index. + * @param {String} indexName name of the index to drop. + * @param {Function} callback for results. + * @return {null} + * @api public + */ +Db.prototype.dropIndex = function(collectionName, indexName, callback) { + this._executeQueryCommand(DbCommand.createDropIndexCommand(this, collectionName, indexName), callback); +}; + +/** + * Reindex all indexes on the collection + * Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections. + * + * @param {String} collectionName the name of the collection. + * @param {Function} callback returns the results. + * @api public +**/ +Db.prototype.reIndex = function(collectionName, callback) { + this._executeQueryCommand(DbCommand.createReIndexCommand(this, collectionName), function(err, result) { + if(err != null) { + callback(err, false); + } else if(result.documents[0].errmsg == null) { + callback(null, true); + } else { + callback(new Error(result.documents[0].errmsg), false); + } + }); +}; + +/** + * Retrieves this collections index info. + * + * Options + * - **full** {Boolean, default:false}, returns the full raw index information. + * + * @param {String} collectionName the name of the collection. + * @param {Object} [options] additional options during update. + * @param {Function} callback returns the index information. + * @return {null} + * @api public + */ +Db.prototype.indexInformation = function(collectionName, options, callback) { + // Unpack calls + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + collectionName = args.length ? args.shift() : null; + options = args.length ? args.shift() : {}; + + // If we specified full information + var full = options['full'] == null ? false : options['full']; + // Build selector for the indexes + var selector = collectionName != null ? {ns: (this.databaseName + "." + collectionName)} : {}; + // Iterate through all the fields of the index + new Cursor(this, new Collection(this, DbCommand.SYSTEM_INDEX_COLLECTION), selector).toArray(function(err, indexes) { + if(err != null) return callback(err, null); + // Contains all the information + var info = {}; + + // if full defined just return all the indexes directly + if(full) return callback(null, indexes); + + // Process all the indexes + for(var i = 0; i < indexes.length; i++) { + var index = indexes[i]; + // Let's unpack the object + info[index.name] = []; + for(var name in index.key) { + info[index.name].push([name, index.key[name]]); + } + } + + // Return all the indexes + callback(null, info); + }); +}; + +/** + * Drop a database. + * + * @param {Function} callback returns the index information. + * @return {null} + * @api public + */ +Db.prototype.dropDatabase = function(callback) { + var self = this; + + this._executeQueryCommand(DbCommand.createDropDatabaseCommand(this), function(err, result) { + if (err == null && result.documents[0].ok == 1) { + callback(null, true); + } else { + if (err) { + callback(err, false); + } else { + callback(self.wrap(result.documents[0]), false); + } + } + }); +}; + +/** + * Register a handler + * @ignore + * @api private + */ +Db.prototype._registerHandler = function(db_command, raw, connection, callback) { + // If we have an array of commands, chain them + var chained = Array.isArray(db_command); + + // If they are chained we need to add a special handler situation + if(chained) { + // List off chained id's + var chainedIds = []; + // Add all id's + for(var i = 0; i < db_command.length; i++) chainedIds.push(db_command[i].getRequestId().toString()); + + // Register all the commands together + for(var i = 0; i < db_command.length; i++) { + var command = db_command[i]; + // Add the callback to the store + this._callBackStore.once(command.getRequestId(), callback); + // Add the information about the reply + this._callBackStore._notReplied[command.getRequestId().toString()] = {start: new Date().getTime(), 'raw': raw, chained:chainedIds, connection:connection}; + } + } else { + // Add the callback to the list of handlers + this._callBackStore.once(db_command.getRequestId(), callback); + // Add the information about the reply + this._callBackStore._notReplied[db_command.getRequestId().toString()] = {start: new Date().getTime(), 'raw': raw, connection:connection}; + } +} + +/** + * + * @ignore + * @api private + */ +Db.prototype._callHandler = function(id, document, err) { + // If there is a callback peform it + if(this._callBackStore.listeners(id).length >= 1) { + // Get info object + var info = this._callBackStore._notReplied[id]; + // Delete the current object + delete this._callBackStore._notReplied[id]; + // Emit to the callback of the object + this._callBackStore.emit(id, err, document, info.connection); + } +} + +/** + * + * @ignore + * @api private + */ +Db.prototype._hasHandler = function(id) { + // If there is a callback peform it + return this._callBackStore.listeners(id).length >= 1; +} + +/** + * + * @ignore + * @api private + */ +Db.prototype._removeHandler = function(id) { + // Remove the information + if(this._callBackStore._notReplied[id] != null) delete this._callBackStore._notReplied[id]; + // Remove the callback if it's registered + this._callBackStore.removeAllListeners(id); + // Force cleanup _events, node.js seems to set it as a null value + if(this._callBackStore._events != null) delete this._callBackStore._events[id]; +} + +/** + * + * @ignore + * @api private + */ +Db.prototype._findHandler = function(id) { + var info = this._callBackStore._notReplied[id]; + // Return the callback + return {info:info, callback:(this._callBackStore.listeners(id).length >= 1)} +} + +/** + * @ignore + */ +var __executeQueryCommand = function(self, db_command, options, callback) { + // Options unpacking + var read = options['read'] != null ? options['read'] : false; + var raw = options['raw'] != null ? options['raw'] : self.raw; + var onAll = options['onAll'] != null ? options['onAll'] : false; + var specifiedConnection = options['connection'] != null ? options['connection'] : null; + + // If we got a callback object + if(typeof callback === 'function' && !onAll) { + // Fetch either a reader or writer dependent on the specified read option + var connection = read == true || read === 'secondary' ? self.serverConfig.checkoutReader() : self.serverConfig.checkoutWriter(true); + // Override connection if needed + connection = specifiedConnection != null ? specifiedConnection : connection; + // Ensure we have a valid connection + if(connection == null) { + return callback(new Error("no open connections")); + } else if(connection instanceof Error) { + return callback(connection); + } + + // Perform reaping of any dead connection + if(self.reaperEnabled) reaper(self, self.reaperInterval, self.reaperTimeout); + + // Register the handler in the data structure + self._registerHandler(db_command, raw, connection, callback); + + // Write the message out and handle any errors if there are any + connection.write(db_command, function(err) { + if(err != null) { + // Call the handler with an error + self._callHandler(db_command.getRequestId(), null, err); + } + }); + } else if(typeof callback === 'function' && onAll) { + var connections = self.serverConfig.allRawConnections(); + var numberOfEntries = connections.length; + // Go through all the connections + for(var i = 0; i < connections.length; i++) { + // Fetch a connection + var connection = connections[i]; + // Override connection if needed + connection = specifiedConnection != null ? specifiedConnection : connection; + // Ensure we have a valid connection + if(connection == null) { + return callback(new Error("no open connections")); + } else if(connection instanceof Error) { + return callback(connection); + } + + // Register the handler in the data structure + self._registerHandler(db_command, raw, connection, callback); + + // Write the message out + connection.write(db_command, function(err) { + // Adjust the number of entries we need to process + numberOfEntries = numberOfEntries - 1; + // Remove listener + if(err != null) { + // Clean up listener and return error + self._removeHandler(db_command.getRequestId()); + } + + // No more entries to process callback with the error + if(numberOfEntries <= 0) { + callback(err); + } + }); + + // Update the db_command request id + db_command.updateRequestId(); + } + } else { + // Fetch either a reader or writer dependent on the specified read option + var connection = read == true || read === 'secondary' ? self.serverConfig.checkoutReader() : self.serverConfig.checkoutWriter(); + // Override connection if needed + connection = specifiedConnection != null ? specifiedConnection : connection; + // Ensure we have a valid connection + if(connection == null || connection instanceof Error) return null; + // Write the message out + connection.write(db_command, function(err) { + if(err != null) { + // Emit the error + self.emit("error", err); + } + }); + } +} + +/** + * @ignore + */ +var __retryCommandOnFailure = function(self, retryInMilliseconds, numberOfTimes, command, db_command, options, callback) { + if(this._state == 'connected' || this._state == 'disconnected') this._state = 'connecting'; + // Number of retries done + var numberOfRetriesDone = numberOfTimes; + // Retry function, execute once + var retryFunction = function(_self, _numberOfRetriesDone, _retryInMilliseconds, _numberOfTimes, _command, _db_command, _options, _callback) { + _self.serverConfig.connect(_self, {}, function(err, result) { + // Adjust the number of retries left + _numberOfRetriesDone = _numberOfRetriesDone - 1; + // Definitively restart + if(err != null && _numberOfRetriesDone > 0) { + _self._state = 'connecting'; + // Force close the current connections + _self.serverConfig.close(function(err) { + // Retry the connect + setTimeout(function() { + retryFunction(_self, _numberOfRetriesDone, _retryInMilliseconds, _numberOfTimes, _command, _db_command, _options, _callback); + }, _retryInMilliseconds); + }); + } else if(err != null && _numberOfRetriesDone <= 0) { + _self._state = 'disconnected'; + // Force close the current connections + _self.serverConfig.close(function(_err) { + // Force close the current connections + _callback(err, null); + }); + } else if(err == null && _self.serverConfig.isConnected() == true && Array.isArray(_self.auths) && _self.auths.length > 0) { + _self._state = 'connected'; + // Get number of auths we need to execute + var numberOfAuths = _self.auths.length; + // Apply all auths + for(var i = 0; i < _self.auths.length; i++) { + _self.authenticate(_self.auths[i].username, _self.auths[i].password, function(err, authenticated) { + numberOfAuths = numberOfAuths - 1; + + // If we have no more authentications to replay + if(numberOfAuths == 0) { + if(err != null || !authenticated) { + return _callback(err, null); + } else { + // Execute command + command(_self, _db_command, _options, function(err, result) { + // Peform the command callback + _callback(err, result); + // Execute any backed up commands + while(_self.commands.length > 0) { + // Fetch the command + var command = _self.commands.shift(); + // Execute based on type + if(command['type'] == 'query') { + __executeQueryCommand(_self, command['db_command'], command['options'], command['callback']); + } else if(command['type'] == 'insert') { + __executeInsertCommand(_self, command['db_command'], command['options'], command['callback']); + } + } + }); + } + } + }); + } + } else if(err == null && _self.serverConfig.isConnected() == true) { + _self._state = 'connected'; + // Execute command + command(_self, _db_command, _options, function(err, result) { + // Peform the command callback + _callback(err, result); + // Execute any backed up commands + while(_self.commands.length > 0) { + // Fetch the command + var command = _self.commands.shift(); + // Execute based on type + if(command['type'] == 'query') { + __executeQueryCommand(_self, command['db_command'], command['options'], command['callback']); + } else if(command['type'] == 'insert') { + __executeInsertCommand(_self, command['db_command'], command['options'], command['callback']); + } + } + }); + } else { + _self._state = 'connecting'; + // Force close the current connections + _self.serverConfig.close(function(err) { + // Retry the connect + setTimeout(function() { + retryFunction(_self, _numberOfRetriesDone, _retryInMilliseconds, _numberOfTimes, _command, _db_command, _options, _callback); + }, _retryInMilliseconds); + }); + } + }); + }; + + // Execute function first time + retryFunction(self, numberOfRetriesDone, retryInMilliseconds, numberOfTimes, command, db_command, options, callback); +} + +/** + * Execute db query command (not safe) + * @ignore + * @api private + */ +Db.prototype._executeQueryCommand = function(db_command, options, callback) { + var self = this; + // Unpack the parameters + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + // Check if the user force closed the command + if(this._applicationClosed) { + if(typeof callback == 'function') { + return callback(new Error("db closed by application"), null); + } else { + throw new Error("db closed by application"); + } + } + + // If the pool is not connected, attemp to reconnect to send the message + if(this._state == 'connecting' && this.serverConfig.autoReconnect) { + process.nextTick(function() { + self.commands.push({type:'query', 'db_command':db_command, 'options':options, 'callback':callback}); + }) + } else if(!this.serverConfig.isConnected() && this.serverConfig.autoReconnect) { + this._state = 'connecting'; + // Retry command + __retryCommandOnFailure(this, this.retryMiliSeconds, this.numberOfRetries, __executeQueryCommand, db_command, options, callback); + } else { + __executeQueryCommand(self, db_command, options, callback) + } +}; + +/** + * @ignore + */ +var __executeInsertCommand = function(self, db_command, options, callback) { + // Always checkout a writer for this kind of operations + var connection = self.serverConfig.checkoutWriter(); + // Get strict mode + var safe = options['safe'] != null ? options['safe'] : false; + var raw = options['raw'] != null ? options['raw'] : self.raw; + var specifiedConnection = options['connection'] != null ? options['connection'] : null; + // Override connection if needed + connection = specifiedConnection != null ? specifiedConnection : connection; + + // Ensure we have a valid connection + if(typeof callback === 'function') { + // Ensure we have a valid connection + if(connection == null) { + return callback(new Error("no open connections")); + } else if(connection instanceof Error) { + return callback(connection); + } + + // We are expecting a check right after the actual operation + if(safe != null && safe != false) { + // db command is now an array of commands (original command + lastError) + db_command = [db_command, DbCommand.createGetLastErrorCommand(safe, self)]; + + // Register the handler in the data structure + self._registerHandler(db_command[1], raw, connection, callback); + } + } + + // If we have no callback and there is no connection + if(connection == null) return null; + if(connection instanceof Error && typeof callback == 'function') return callback(connection, null); + if(connection instanceof Error) return null; + if(connection == null && typeof callback == 'function') return callback(new Error("no primary server found"), null); + + // Write the message out + connection.write(db_command, function(err) { + // Return the callback if it's not a safe operation and the callback is defined + if(typeof callback === 'function' && (safe == null || safe == false)) { + // Perform reaping + if(self.reaperEnabled) reaper(self, self.reaperInterval, self.reaperTimeout); + // Perform the callback + callback(err, null); + } else if(typeof callback === 'function'){ + // Call the handler with an error + self._callHandler(db_command[1].getRequestId(), null, err); + } else { + self.emit("error", err); + } + }); +} + +/** + * Execute an insert Command + * @ignore + * @api private + */ +Db.prototype._executeInsertCommand = function(db_command, options, callback) { + var self = this; + // Unpack the parameters + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + + // Check if the user force closed the command + if(this._applicationClosed) { + if(typeof callback == 'function') { + return callback(new Error("db closed by application"), null); + } else { + throw new Error("db closed by application"); + } + } + + // If the pool is not connected, attemp to reconnect to send the message + if(self._state == 'connecting' && this.serverConfig.autoReconnect) { + process.nextTick(function() { + self.commands.push({type:'insert', 'db_command':db_command, 'options':options, 'callback':callback}); + }) + } else if(!this.serverConfig.isConnected() && this.serverConfig.autoReconnect) { + this._state = 'connecting'; + // Retry command + __retryCommandOnFailure(this, this.retryMiliSeconds, this.numberOfRetries, __executeInsertCommand, db_command, options, callback); + } else { + __executeInsertCommand(self, db_command, options, callback) + } +} + +/** + * Update command is the same + * @ignore + * @api private + */ +Db.prototype._executeUpdateCommand = Db.prototype._executeInsertCommand; +/** + * Remove command is the same + * @ignore + * @api private + */ +Db.prototype._executeRemoveCommand = Db.prototype._executeInsertCommand; + +/** + * Wrap a Mongo error document into an Error instance + * @ignore + * @api private + */ +Db.prototype.wrap = function(error) { + var msg = error.err || error.errmsg || error; + var e = new Error(msg); + e.name = 'MongoError'; + + // Get all object keys + var keys = Object.keys(error); + // Populate error object with properties + for(var i = 0; i < keys.length; i++) { + e[keys[i]] = error[keys[i]]; + } + + return e; +} + +/** + * Default URL + * + * @classconstant DEFAULT_URL + **/ +Db.DEFAULT_URL = 'mongodb://localhost:27017/default'; + +/** + * Connect to MongoDB using a url as documented at + * + * www.mongodb.org/display/DOCS/Connections + * + * @param {String} url connection url for MongoDB. + * @param {Object} options additional options not covered by the url. + * @param {Function} callback callback returns the initialized db. + * @return {null} + * @api public + */ +Db.connect = function(url, options, callback) { + var args = Array.prototype.slice.call(arguments, 1); + callback = typeof args[args.length - 1] == 'function' ? args.pop() : null; + options = args.length ? args.shift() : null; + options = options || {}; + var serverOptions = options.server || {}; + var replSetServersOptions = options.replSetServers || {}; + var dbOptions = options.db || {}; + + var urlRE = new RegExp('^mongo(?:db)?://(?:|([^@/]*)@)([^@/]*)(?:|/([^?]*)(?:|\\?([^?]*)))$'); + var match = (url || Db.DEFAULT_URL).match(urlRE); + if (!match) + throw Error("URL must be in the format mongodb://user:pass@host:port/dbname"); + + var authPart = match[1] || ''; + var auth = authPart.split(':', 2); + var hostPart = match[2]; + var dbname = match[3] || 'default'; + var urlOptions = (match[4] || '').split(/[&;]/); + + // Ugh, we have to figure out which options go to which constructor manually. + urlOptions.forEach(function(opt) { + if (!opt) return; + var splitOpt = opt.split('='), name = splitOpt[0], value = splitOpt[1]; + + // Server options: + if (name == 'slaveOk' || name == 'slave_ok') + serverOptions.slave_ok = (value == 'true'); + if (name == 'poolSize') + serverOptions.poolSize = Number(value); + if (name == 'autoReconnect' || name == 'auto_reconnect') + serverOptions.auto_reconnect = (value == 'true'); + if (name == 'ssl' || name == 'ssl') + serverOptions.ssl = (value == 'true'); + + // ReplSetServers options: + if (name == 'replicaSet' || name == 'rs_name') + replSetServersOptions.rs_name = value; + if (name == 'reconnectWait') + replSetServersOptions.reconnectWait = Number(value); + if (name == 'retries') + replSetServersOptions.retries = Number(value); + if (name == 'readSecondary' || name == 'read_secondary') + replSetServersOptions.read_secondary = (value == 'true'); + + // DB options: + if (name == 'safe') + dbOptions.safe = (value == 'true'); + // Not supported by Db: safe, w, wtimeoutMS, fsync, journal, connectTimeoutMS, socketTimeoutMS + if (name == 'nativeParser' || name == 'native_parser') + dbOptions.native_parser = (value == 'true'); + if (name == 'strict') + dbOptions.strict = (value == 'true'); + }); + + var servers = hostPart.split(',').map(function(h) { + var hostPort = h.split(':', 2); + return new Server(hostPort[0] || 'localhost', hostPort[1] != null ? parseInt(hostPort[1]) : 27017, serverOptions); + }); + + var server; + if (servers.length == 1) { + server = servers[0]; + } else { + server = new ReplSetServers(servers, replSetServersOptions); + } + + var db = new Db(dbname, server, dbOptions); + if(options.noOpen) + return db; + + // If callback is null throw an exception + if(callback == null) throw new Error("no callback function provided"); + + db.open(function(err, db){ + if(err == null && authPart){ + db.authenticate(auth[0], auth[1], function(err, success){ + if(success){ + callback(null, db); + } else { + callback(err ? err : new Error('Could not authenticate user ' + auth[0]), db); + } + }); + } else { + callback(err, db); + } + }); +} + +/** + * Legacy support + * + * @ignore + * @api private + */ +exports.connect = Db.connect; +exports.Db = Db; + +/** + * Remove all listeners to the db instance. + * @ignore + * @api private + */ +Db.prototype.removeAllEventListeners = function() { + this.removeAllListeners("close"); + this.removeAllListeners("error"); + this.removeAllListeners("timeout"); + this.removeAllListeners("parseError"); + this.removeAllListeners("poolReady"); + this.removeAllListeners("message"); +} diff --git a/node_modules/mongodb/lib/mongodb/gridfs/chunk.js b/node_modules/mongodb/lib/mongodb/gridfs/chunk.js new file mode 100644 index 0000000..cfb9009 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/gridfs/chunk.js @@ -0,0 +1,208 @@ +var Binary = require('bson').Binary, + ObjectID = require('bson').ObjectID; + +/** + * Class for representing a single chunk in GridFS. + * + * @class + * + * @param file {GridStore} The {@link GridStore} object holding this chunk. + * @param mongoObject {object} The mongo object representation of this chunk. + * + * @throws Error when the type of data field for {@link mongoObject} is not + * supported. Currently supported types for data field are instances of + * {@link String}, {@link Array}, {@link Binary} and {@link Binary} + * from the bson module + * + * @see Chunk#buildMongoObject + */ +var Chunk = exports.Chunk = function(file, mongoObject) { + if(!(this instanceof Chunk)) return new Chunk(file, mongoObject); + + this.file = file; + var self = this; + var mongoObjectFinal = mongoObject == null ? {} : mongoObject; + + this.objectId = mongoObjectFinal._id == null ? new ObjectID() : mongoObjectFinal._id; + this.chunkNumber = mongoObjectFinal.n == null ? 0 : mongoObjectFinal.n; + this.data = new Binary(); + + if(mongoObjectFinal.data == null) { + } else if(typeof mongoObjectFinal.data == "string") { + var buffer = new Buffer(mongoObjectFinal.data.length); + buffer.write(mongoObjectFinal.data, 'binary', 0); + this.data = new Binary(buffer); + } else if(Array.isArray(mongoObjectFinal.data)) { + var buffer = new Buffer(mongoObjectFinal.data.length); + buffer.write(mongoObjectFinal.data.join(''), 'binary', 0); + this.data = new Binary(buffer); + } else if(mongoObjectFinal.data instanceof Binary || Object.prototype.toString.call(mongoObjectFinal.data) == "[object Binary]") { + this.data = mongoObjectFinal.data; + } else if(Buffer.isBuffer(mongoObjectFinal.data)) { + } else { + throw Error("Illegal chunk format"); + } + // Update position + this.internalPosition = 0; + + /** + * The position of the read/write head + * @name position + * @lends Chunk# + * @field + */ + Object.defineProperty(this, "position", { enumerable: true + , get: function () { + return this.internalPosition; + } + , set: function(value) { + this.internalPosition = value; + } + }); +}; + +/** + * Writes a data to this object and advance the read/write head. + * + * @param data {string} the data to write + * @param callback {function(*, GridStore)} This will be called after executing + * this method. The first parameter will contain null and the second one + * will contain a reference to this object. + */ +Chunk.prototype.write = function(data, callback) { + this.data.write(data, this.internalPosition); + this.internalPosition = this.data.length(); + callback(null, this); +}; + +/** + * Reads data and advances the read/write head. + * + * @param length {number} The length of data to read. + * + * @return {string} The data read if the given length will not exceed the end of + * the chunk. Returns an empty String otherwise. + */ +Chunk.prototype.read = function(length) { + // Default to full read if no index defined + length = length == null || length == 0 ? this.length() : length; + + if(this.length() - this.internalPosition + 1 >= length) { + var data = this.data.read(this.internalPosition, length); + this.internalPosition = this.internalPosition + length; + return data; + } else { + return ''; + } +}; + +Chunk.prototype.readSlice = function(length) { + if ((this.length() - this.internalPosition + 1) >= length) { + var data = null; + if (this.data.buffer != null) { //Pure BSON + data = this.data.buffer.slice(this.internalPosition, this.internalPosition + length); + } else { //Native BSON + data = new Buffer(length); + length = this.data.readInto(data, this.internalPosition); + } + this.internalPosition = this.internalPosition + length; + return data; + } else { + return null; + } +}; + +/** + * Checks if the read/write head is at the end. + * + * @return {boolean} Whether the read/write head has reached the end of this + * chunk. + */ +Chunk.prototype.eof = function() { + return this.internalPosition == this.length() ? true : false; +}; + +/** + * Reads one character from the data of this chunk and advances the read/write + * head. + * + * @return {string} a single character data read if the the read/write head is + * not at the end of the chunk. Returns an empty String otherwise. + */ +Chunk.prototype.getc = function() { + return this.read(1); +}; + +/** + * Clears the contents of the data in this chunk and resets the read/write head + * to the initial position. + */ +Chunk.prototype.rewind = function() { + this.internalPosition = 0; + this.data = new Binary(); +}; + +/** + * Saves this chunk to the database. Also overwrites existing entries having the + * same id as this chunk. + * + * @param callback {function(*, GridStore)} This will be called after executing + * this method. The first parameter will contain null and the second one + * will contain a reference to this object. + */ +Chunk.prototype.save = function(callback) { + var self = this; + + self.file.chunkCollection(function(err, collection) { + collection.remove({'_id':self.objectId}, {safe:true}, function(err, result) { + if(self.data.length() > 0) { + self.buildMongoObject(function(mongoObject) { + collection.insert(mongoObject, {safe:true}, function(err, collection) { + callback(null, self); + }); + }); + } else { + callback(null, self); + } + }); + }); +}; + +/** + * Creates a mongoDB object representation of this chunk. + * + * @param callback {function(Object)} This will be called after executing this + * method. The object will be passed to the first parameter and will have + * the structure: + * + *

+ *        {
+ *          '_id' : , // {number} id for this chunk
+ *          'files_id' : , // {number} foreign key to the file collection
+ *          'n' : , // {number} chunk number
+ *          'data' : , // {bson#Binary} the chunk data itself
+ *        }
+ *        
+ * + * @see MongoDB GridFS Chunk Object Structure + */ +Chunk.prototype.buildMongoObject = function(callback) { + var mongoObject = {'_id': this.objectId, + 'files_id': this.file.fileId, + 'n': this.chunkNumber, + 'data': this.data}; + callback(mongoObject); +}; + +/** + * @return {number} the length of the data + */ +Chunk.prototype.length = function() { + return this.data.length(); +}; + +/** + * The default chunk size + * @constant + */ +Chunk.DEFAULT_CHUNK_SIZE = 1024 * 256; diff --git a/node_modules/mongodb/lib/mongodb/gridfs/grid.js b/node_modules/mongodb/lib/mongodb/gridfs/grid.js new file mode 100644 index 0000000..d42c3d6 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/gridfs/grid.js @@ -0,0 +1,98 @@ +var GridStore = require('./gridstore').GridStore, + ObjectID = require('bson').ObjectID; + +/** + * A class representation of a simple Grid interface. + * + * @class Represents the Grid. + * @param {Db} db A database instance to interact with. + * @param {String} [fsName] optional different root collection for GridFS. + * @return {Grid} + */ +function Grid(db, fsName) { + + if(!(this instanceof Grid)) return new Grid(db, fsName); + + this.db = db; + this.fsName = fsName == null ? GridStore.DEFAULT_ROOT_COLLECTION : fsName; +} + +/** + * Puts binary data to the grid + * + * @param {Buffer} data buffer with Binary Data. + * @param {Object} [options] the options for the files. + * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. + * @return {null} + * @api public + */ +Grid.prototype.put = function(data, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + options = args.length ? args.shift() : {}; + // If root is not defined add our default one + options['root'] = options['root'] == null ? this.fsName : options['root']; + + // Return if we don't have a buffer object as data + if(!(Buffer.isBuffer(data))) return callback(new Error("Data object must be a buffer object"), null); + // Get filename if we are using it + var filename = options['filename']; + // Create gridstore + var gridStore = new GridStore(this.db, filename, "w", options); + gridStore.open(function(err, gridStore) { + if(err) return callback(err, null); + + gridStore.write(data, function(err, result) { + if(err) return callback(err, null); + + gridStore.close(function(err, result) { + if(err) return callback(err, null); + callback(null, result); + }) + }) + }) +} + +/** + * Get binary data to the grid + * + * @param {ObjectID} id ObjectID for file. + * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. + * @return {null} + * @api public + */ +Grid.prototype.get = function(id, callback) { + // Validate that we have a valid ObjectId + if(!(id instanceof ObjectID)) return callback(new Error("Not a valid ObjectID", null)); + // Create gridstore + var gridStore = new GridStore(this.db, id, "r", {root:this.fsName}); + gridStore.open(function(err, gridStore) { + if(err) return callback(err, null); + + // Return the data + gridStore.read(function(err, data) { + return callback(err, data) + }); + }) +} + +/** + * Delete file from grid + * + * @param {ObjectID} id ObjectID for file. + * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. + * @return {null} + * @api public + */ +Grid.prototype.delete = function(id, callback) { + // Validate that we have a valid ObjectId + if(!(id instanceof ObjectID)) return callback(new Error("Not a valid ObjectID", null)); + // Create gridstore + GridStore.unlink(this.db, id, {root:this.fsName}, function(err, result) { + if(err) return callback(err, false); + return callback(null, true); + }); +} + +exports.Grid = Grid; diff --git a/node_modules/mongodb/lib/mongodb/gridfs/gridstore.js b/node_modules/mongodb/lib/mongodb/gridfs/gridstore.js new file mode 100644 index 0000000..6b56cf1 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/gridfs/gridstore.js @@ -0,0 +1,1109 @@ +/** + * @fileOverview GridFS is a tool for MongoDB to store files to the database. + * Because of the restrictions of the object size the database can hold, a + * facility to split a file into several chunks is needed. The {@link GridStore} + * class offers a simplified api to interact with files while managing the + * chunks of split files behind the scenes. More information about GridFS can be + * found here. + */ +var Chunk = require('./chunk').Chunk, + DbCommand = require('../commands/db_command').DbCommand, + ObjectID = require('bson').ObjectID, + Buffer = require('buffer').Buffer, + fs = require('fs'), + util = require('util'), + ReadStream = require('./readstream').ReadStream; + +var REFERENCE_BY_FILENAME = 0, + REFERENCE_BY_ID = 1; + +/** + * A class representation of a file stored in GridFS. + * + * Modes + * - **"r"** - read only. This is the default mode. + * - **"w"** - write in truncate mode. Existing data will be overwriten. + * - **w+"** - write in edit mode. + * + * Options + * - **root** {String}, root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. + * - **chunk_type** {String}, mime type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**. + * - **chunk_size** {Number}, size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**. + * - **metadata** {Object}, arbitrary data the user wants to store. + * + * @class Represents the GridStore. + * @param {Db} db A database instance to interact with. + * @param {ObjectID} id an unique ObjectID for this file + * @param {String} [filename] optional a filename for this file, no unique constrain on the field + * @param {String} mode set the mode for this file. + * @param {Object} options optional properties to specify. Recognized keys: + * @return {GridStore} + */ +function GridStore(db, id, filename, mode, options) { + if(!(this instanceof GridStore)) return new GridStore(db, id, filename, mode, options); + + var self = this; + this.db = db; + var _filename = filename; + + if(typeof filename == 'string' && typeof mode == 'string') { + _filename = filename; + } else if(typeof filename == 'string' && typeof mode == 'object' && mode != null) { + var _mode = mode; + mode = filename; + options = _mode; + _filename = id; + } else if(typeof filename == 'string' && mode == null) { + mode = filename; + _filename = id; + } + + // set grid referencetype + this.referenceBy = typeof id == 'string' ? 0 : 1; + this.filename = _filename; + this.fileId = id; + + // Set up the rest + this.mode = mode == null ? "r" : mode; + this.options = options == null ? {} : options; + this.root = this.options['root'] == null ? exports.GridStore.DEFAULT_ROOT_COLLECTION : this.options['root']; + this.position = 0; + // Set default chunk size + this.internalChunkSize = this.options['chunkSize'] == null ? Chunk.DEFAULT_CHUNK_SIZE : this.options['chunkSize']; + + /** + * Returns the current chunksize of the file. + * + * @field chunkSize + * @type {Number} + * @getter + * @setter + * @property return number of bytes in the current chunkSize. + */ + Object.defineProperty(this, "chunkSize", { enumerable: true + , get: function () { + return this.internalChunkSize; + } + , set: function(value) { + if(!(this.mode[0] == "w" && this.position == 0 && this.uploadDate == null)) { + this.internalChunkSize = this.internalChunkSize; + } else { + this.internalChunkSize = value; + } + } + }); + + /** + * The md5 checksum for this file. + * + * @field md5 + * @type {Number} + * @getter + * @setter + * @property return this files md5 checksum. + */ + Object.defineProperty(this, "md5", { enumerable: true + , get: function () { + return this.internalMd5; + } + }); +}; + +/** + * Opens the file from the database and initialize this object. Also creates a + * new one if file does not exist. + * + * @param {Function} callback this will be called after executing this method. The first parameter will contain an **{Error}** object and the second parameter will be null if an error occured. Otherwise, the first parameter will be null and the second will contain the reference to this object. + * @return {null} + * @api public + */ +GridStore.prototype.open = function(callback) { + if( this.mode != "w" && this.mode != "w+" && this.mode != "r"){ + callback(new Error("Illegal mode " + this.mode), null); + return; + } + + var self = this; + + if((self.mode == "w" || self.mode == "w+") && self.db.serverConfig.primary != null) { + // Get files collection + self.collection(function(err, collection) { + // Get chunk collection + self.chunkCollection(function(err, chunkCollection) { + // Ensure index on chunk collection + chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) { + _open(self, callback); + }); + }); + }); + } else { + _open(self, callback); + } +} + +/** + * Hidding the _open function + * @ignore + * @api private + */ +var _open = function(self, callback) { + self.collection(function(err, collection) { + if(err!==null) { + callback(new Error("at collection: "+err), null); + return; + } + + // Create the query + var query = self.referenceBy == REFERENCE_BY_ID ? {_id:self.fileId} : {filename:self.filename}; + query = self.fileId == null && this.filename == null ? null : query; + + // Fetch the chunks + if(query != null) { + collection.find(query, function(err, cursor) { + // Fetch the file + cursor.nextObject(function(err, doc) { + // Chek if the collection for the files exists otherwise prepare the new one + if(doc != null) { + self.fileId = doc._id; + self.contentType = doc.contentType; + self.internalChunkSize = doc.chunkSize; + self.uploadDate = doc.uploadDate; + self.aliases = doc.aliases; + self.length = doc.length; + self.metadata = doc.metadata; + self.internalMd5 = doc.md5; + } else { + self.fileId = self.fileId instanceof ObjectID ? self.fileId : new ObjectID(); + self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE; + self.internalChunkSize = self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize; + self.length = 0; + } + + // Process the mode of the object + if(self.mode == "r") { + nthChunk(self, 0, function(err, chunk) { + self.currentChunk = chunk; + self.position = 0; + callback(null, self); + }); + } else if(self.mode == "w") { + // Delete any existing chunks + deleteChunks(self, function(err, result) { + self.currentChunk = new Chunk(self, {'n':0}); + self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; + self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; + self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; + self.position = 0; + callback(null, self); + }); + } else if(self.mode == "w+") { + nthChunk(self, lastChunkNumber(self), function(err, chunk) { + // Set the current chunk + self.currentChunk = chunk == null ? new Chunk(self, {'n':0}) : chunk; + self.currentChunk.position = self.currentChunk.data.length(); + self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; + self.position = self.length; + callback(null, self); + }); + } + }); + }); + } else { + // Write only mode + self.fileId = new ObjectID(); + self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE; + self.internalChunkSize = self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize; + self.length = 0; + + self.chunkCollection(function(err, collection2) { + // No file exists set up write mode + if(self.mode == "w") { + // Delete any existing chunks + deleteChunks(self, function(err, result) { + self.currentChunk = new Chunk(self, {'n':0}); + self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; + self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; + self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; + self.position = 0; + callback(null, self); + }); + } else if(self.mode == "w+") { + nthChunk(self, lastChunkNumber(self), function(err, chunk) { + // Set the current chunk + self.currentChunk = chunk == null ? new Chunk(self, {'n':0}) : chunk; + self.currentChunk.position = self.currentChunk.data.length(); + self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; + self.position = self.length; + callback(null, self); + }); + } + }); + }; + }); +}; + +/** + * Stores a file from the file system to the GridFS database. + * + * @param {String|Buffer|FileHandle} file the file to store. + * @param {Function} callback this will be called after this method is executed. The first parameter will be null and the the second will contain the reference to this object. + * @return {null} + * @api public + */ +GridStore.prototype.writeFile = function (file, callback) { + var self = this; + if (typeof file === 'string') { + fs.open(file, 'r', 0666, function (err, fd) { + // TODO Handle err + self.writeFile(fd, callback); + }); + return; + } + + self.open(function (err, self) { + fs.fstat(file, function (err, stats) { + var offset = 0; + var index = 0; + var numberOfChunksLeft = Math.min(stats.size / self.chunkSize); + + // Write a chunk + var writeChunk = function() { + fs.read(file, self.chunkSize, offset, 'binary', function(err, data, bytesRead) { + offset = offset + bytesRead; + // Create a new chunk for the data + var chunk = new Chunk(self, {n:index++}); + chunk.write(data, function(err, chunk) { + chunk.save(function(err, result) { + // Point to current chunk + self.currentChunk = chunk; + + if(offset >= stats.size) { + fs.close(file); + self.close(function(err, result) { + return callback(null, result); + }) + } else { + return process.nextTick(writeChunk); + } + }); + }); + }); + } + + // Process the first write + process.nextTick(writeChunk); + }); + }); +}; + +/** + * Writes some data. This method will work properly only if initialized with mode "w" or "w+". + * + * @param {String|Buffer} data the data to write. + * @param {Boolean} [close] closes this file after writing if set to true. + * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. + * @return {null} + * @api public + */ +GridStore.prototype.write = function(data, close, callback) { + // If we have a buffer write it using the writeBuffer method + if(Buffer.isBuffer(data)) return writeBuffer(this, data, close, callback); + // Otherwise check for the callback + if(typeof close === "function") { callback = close; close = null; } + var self = this; + var finalClose = close == null ? false : close; + // Otherwise let's write the data + if(self.mode[0] != "w") { + callback(new Error((self.referenceBy == REFERENCE_BY_ID ? self.toHexString() : self.filename) + " not opened for writing"), null); + } else { + if((self.currentChunk.position + data.length) > self.chunkSize) { + var previousChunkNumber = self.currentChunk.chunkNumber; + var leftOverDataSize = self.chunkSize - self.currentChunk.position; + var previousChunkData = data.slice(0, leftOverDataSize); + var leftOverData = data.slice(leftOverDataSize, (data.length - leftOverDataSize)); + // Save out current Chunk as another variable and assign a new Chunk for overflow data + var saveChunk = self.currentChunk; + // Create a new chunk at once (avoid wrong writing of chunks) + self.currentChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}); + + // Let's finish the current chunk and then call write again for the remaining data + saveChunk.write(previousChunkData, function(err, chunk) { + chunk.save(function(err, result) { + self.position = self.position + leftOverDataSize; + // Write the remaining data + self.write(leftOverData, function(err, gridStore) { + if(finalClose) { + self.close(function(err, result) { + callback(null, gridStore); + }); + } else { + callback(null, gridStore); + } + }); + }); + }); + } else { + self.currentChunk.write(data, function(err, chunk) { + self.position = self.position + data.length; + if(finalClose) { + self.close(function(err, result) { + callback(null, self); + }); + } else { + callback(null, self); + } + }); + } + } +}; + +/** + * Writes some data. This method will work properly only if initialized with mode + * "w" or "w+". + * + * @param string {string} The data to write. + * @param close {boolean=false} opt_argument Closes this file after writing if + * true. + * @param callback {function(*, GridStore)} This will be called after executing + * this method. The first parameter will contain null and the second one + * will contain a reference to this object. + * + * @ignore + * @api private + */ +var writeBuffer = function(self, buffer, close, callback) { + if(typeof close === "function") { callback = close; close = null; } + var finalClose = (close == null) ? false : close; + + if(self.mode[0] != "w") { + callback(new Error((self.referenceBy == REFERENCE_BY_ID ? self.toHexString() : self.filename) + " not opened for writing"), null); + } else { + if((self.currentChunk.position + buffer.length) > self.chunkSize) { + // Data exceeds current chunk remaining free size; fill up current chunk and write the rest + // to a new chunk (recursively) + var previousChunkNumber = self.currentChunk.chunkNumber; + var leftOverDataSize = self.chunkSize - self.currentChunk.position; + var firstChunkData = buffer.slice(0, leftOverDataSize); + var leftOverData = buffer.slice(leftOverDataSize); + // Save out current Chunk as another variable and assign a new Chunk for overflow data + var saveChunk = self.currentChunk; + // Create a new chunk at once (avoid wrong writing of chunks) + self.currentChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}); + + // Let's finish the current chunk and then call write again for the remaining data + saveChunk.write(firstChunkData, function(err, chunk) { + chunk.save(function(err, result) { + self.position = self.position + leftOverDataSize; + + // Write the remaining data + writeBuffer(self, leftOverData, function(err, gridStore) { + if(finalClose) { + self.close(function(err, result) { + callback(null, gridStore); + }); + } + else { + callback(null, gridStore); + } + }); + }); + }); + } + else { + // Write buffer to chunk all at once + self.currentChunk.write(buffer, function(err, chunk) { + self.position = self.position + buffer.length; + if(finalClose) { + self.close(function(err, result) { + callback(null, self); + }); + } + else { + callback(null, self); + } + }); + } + } +}; + +/** + * Creates a mongoDB object representation of this object. + * + * @param callback {function(object)} This will be called after executing this + * method. The object will be passed to the first parameter and will have + * the structure: + * + *

+ *        {
+ *          '_id' : , // {number} id for this file
+ *          'filename' : , // {string} name for this file
+ *          'contentType' : , // {string} mime type for this file
+ *          'length' : , // {number} size of this file?
+ *          'chunksize' : , // {number} chunk size used by this file
+ *          'uploadDate' : , // {Date}
+ *          'aliases' : , // {array of string}
+ *          'metadata' : , // {string}
+ *        }
+ *        
+ * + * @ignore + * @api private + */ +var buildMongoObject = function(self, callback) { + var length = self.currentChunk != null ? (self.currentChunk.chunkNumber * self.chunkSize + self.currentChunk.position) : 0; + var mongoObject = { + '_id': self.fileId, + 'filename': self.filename, + 'contentType': self.contentType, + 'length': length < 0 ? 0 : length, + 'chunkSize': self.chunkSize, + 'uploadDate': self.uploadDate, + 'aliases': self.aliases, + 'metadata': self.metadata + }; + + var md5Command = {filemd5:self.fileId, root:self.root}; + self.db.command(md5Command, function(err, results) { + mongoObject.md5 = results.md5; + callback(mongoObject); + }); +}; + +/** + * Saves this file to the database. This will overwrite the old entry if it + * already exists. This will work properly only if mode was initialized to + * "w" or "w+". + * + * @param {Function} callback this will be called after executing this method. Passes an **{Error}** object to the first parameter and null to the second if an error occured. Otherwise, passes null to the first and a reference to this object to the second. + * @return {null} + * @api public + */ +GridStore.prototype.close = function(callback) { + var self = this; + + if(self.mode[0] == "w") { + if(self.currentChunk != null && self.currentChunk.position > 0) { + self.currentChunk.save(function(err, chuck) { + self.collection(function(err, files) { + // Build the mongo object + if(self.uploadDate != null) { + files.remove({'_id':self.fileId}, {safe:true}, function(err, collection) { + buildMongoObject(self, function(mongoObject) { + files.save(mongoObject, {safe:true}, function(err, doc) { + callback(err, mongoObject); + }); + }); + }); + } else { + self.uploadDate = new Date(); + buildMongoObject(self, function(mongoObject) { + files.save(mongoObject, {safe:true}, function(err, doc) { + callback(err, mongoObject); + }); + }); + } + }); + }); + } else { + self.collection(function(err, files) { + self.uploadDate = new Date(); + buildMongoObject(self, function(mongoObject) { + files.save(mongoObject, {safe:true}, function(err, doc) { + callback(err, mongoObject); + }); + }); + }); + } + } else if(self.mode[0] == "r") { + callback(null, null); + } else { + callback(new Error("Illegal mode " + self.mode), null); + } +}; + +/** + * Gets the nth chunk of this file. + * + * @param chunkNumber {number} The nth chunk to retrieve. + * @param callback {function(*, Chunk|object)} This will be called after + * executing this method. null will be passed to the first parameter while + * a new {@link Chunk} instance will be passed to the second parameter if + * the chunk was found or an empty object {} if not. + * + * @ignore + * @api private + */ +var nthChunk = function(self, chunkNumber, callback) { + self.chunkCollection(function(err, collection) { + collection.find({'files_id':self.fileId, 'n':chunkNumber}, function(err, cursor) { + cursor.nextObject(function(err, chunk) { + var finalChunk = chunk == null ? {} : chunk; + callback(null, new Chunk(self, finalChunk)); + }); + }); + }); +}; + +/** + * + * @ignore + * @api private + */ +GridStore.prototype._nthChunk = function(chunkNumber, callback) { + nthChunk(this, chunkNumber, callback); +} + +/** + * @return {Number} The last chunk number of this file. + * + * @ignore + * @api private + */ +var lastChunkNumber = function(self) { + return Math.floor(self.length/self.chunkSize); +}; + +/** + * Retrieve this file's chunks collection. + * + * @param {Function} callback this will be called after executing this method. An exception object will be passed to the first parameter when an error occured or null otherwise. A new **{Collection}** object will be passed to the second parameter if no error occured. + * @return {null} + * @api public + */ +GridStore.prototype.chunkCollection = function(callback) { + this.db.collection((this.root + ".chunks"), callback); +}; + +/** + * Deletes all the chunks of this file in the database. + * + * @param callback {function(*, boolean)} This will be called after this method + * executes. Passes null to the first and true to the second argument. + * + * @ignore + * @api private + */ +var deleteChunks = function(self, callback) { + if(self.fileId != null) { + self.chunkCollection(function(err, collection) { + if(err!==null) { + callback(err, false); + } + collection.remove({'files_id':self.fileId}, {safe:true}, function(err, result) { + callback(null, true); + }); + }); + } else { + callback(null, true); + } +}; + +/** + * Deletes all the chunks of this file in the database. + * + * @param {Function} callback this will be called after this method executes. Passes null to the first and true to the second argument. + * @return {null} + * @api public + */ +GridStore.prototype.unlink = function(callback) { + var self = this; + deleteChunks(this, function(err) { + if(err!==null) { + callback("at deleteChunks: "+err); + return; + } + + self.collection(function(err, collection) { + if(err!==null) { + callback("at collection: "+err); + return; + } + + collection.remove({'_id':self.fileId}, {safe:true}, function(err, collection) { + callback(err, self); + }); + }); + }); +}; + +/** + * Retrieves the file collection associated with this object. + * + * @param {Function} callback this will be called after executing this method. An exception object will be passed to the first parameter when an error occured or null otherwise. A new **{Collection}** object will be passed to the second parameter if no error occured. + * @return {null} + * @api public + */ +GridStore.prototype.collection = function(callback) { + this.db.collection(this.root + ".files", function(err, collection) { + callback(err, collection); + }); +}; + +/** + * Reads the data of this file. + * + * @param {String} [separator] the character to be recognized as the newline separator. + * @param {Function} callback This will be called after this method is executed. The first parameter will be null and the second parameter will contain an array of strings representing the entire data, each element representing a line including the separator character. + * @return {null} + * @api public + */ +GridStore.prototype.readlines = function(separator, callback) { + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + separator = args.length ? args.shift() : "\n"; + + this.read(function(err, data) { + var items = data.toString().split(separator); + items = items.length > 0 ? items.splice(0, items.length - 1) : []; + for(var i = 0; i < items.length; i++) { + items[i] = items[i] + separator; + } + + callback(null, items); + }); +}; + +/** + * Deletes all the chunks of this file in the database if mode was set to "w" or + * "w+" and resets the read/write head to the initial position. + * + * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. + * @return {null} + * @api public + */ +GridStore.prototype.rewind = function(callback) { + var self = this; + + if(this.currentChunk.chunkNumber != 0) { + if(this.mode[0] == "w") { + deleteChunks(self, function(err, gridStore) { + self.currentChunk = new Chunk(self, {'n': 0}); + self.position = 0; + callback(null, self); + }); + } else { + self.currentChunk(0, function(err, chunk) { + self.currentChunk = chunk; + self.currentChunk.rewind(); + self.position = 0; + callback(null, self); + }); + } + } else { + self.currentChunk.rewind(); + self.position = 0; + callback(null, self); + } +}; + +/** + * Retrieves the contents of this file and advances the read/write head. Works with Buffers only. + * + * There are 3 signatures for this method: + * + * (callback) + * (length, callback) + * (length, buffer, callback) + * + * @param {Number} [length] the number of characters to read. Reads all the characters from the read/write head to the EOF if not specified. + * @param {String|Buffer} [buffer] a string to hold temporary data. This is used for storing the string data read so far when recursively calling this method. + * @param {Function} callback this will be called after this method is executed. null will be passed to the first parameter and a string containing the contents of the buffer concatenated with the contents read from this file will be passed to the second. + * @return {null} + * @api public + */ +GridStore.prototype.read = function(length, buffer, callback) { + var self = this; + + var args = Array.prototype.slice.call(arguments, 0); + callback = args.pop(); + length = args.length ? args.shift() : null; + buffer = args.length ? args.shift() : null; + + // The data is a c-terminated string and thus the length - 1 + var finalLength = length == null ? self.length - self.position : length; + var finalBuffer = buffer == null ? new Buffer(finalLength) : buffer; + // Add a index to buffer to keep track of writing position or apply current index + finalBuffer._index = buffer != null && buffer._index != null ? buffer._index : 0; + + if((self.currentChunk.length() - self.currentChunk.position + 1 + finalBuffer._index) >= finalLength) { + var slice = self.currentChunk.readSlice(finalLength - finalBuffer._index); + // Copy content to final buffer + slice.copy(finalBuffer, finalBuffer._index); + // Update internal position + self.position = finalBuffer.length; + // Check if we don't have a file at all + if(finalLength == 0 && finalBuffer.length == 0) return callback(new Error("File does not exist"), null); + // Else return data + callback(null, finalBuffer); + } else { + var slice = self.currentChunk.readSlice(self.currentChunk.length()); + // Copy content to final buffer + slice.copy(finalBuffer, finalBuffer._index); + // Update index position + finalBuffer._index += slice.length; + + // Load next chunk and read more + nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) { + if(chunk.length() > 0) { + self.currentChunk = chunk; + self.read(length, finalBuffer, callback); + } else { + finalBuffer._index > 0 ? callback(null, finalBuffer) : callback(new Error("no chunks found for file, possibly corrupt"), null); + } + }); + } +} + +/** + * Retrieves the position of the read/write head of this file. + * + * @param {Function} callback This gets called after this method terminates. null is passed to the first parameter and the position is passed to the second. + * @return {null} + * @api public + */ +GridStore.prototype.tell = function(callback) { + callback(null, this.position); +}; + +/** + * Moves the read/write head to a new location. + * + * There are 3 signatures for this method + * + * Seek Location Modes + * - **GridStore.IO_SEEK_SET**, **(default)** set the position from the start of the file. + * - **GridStore.IO_SEEK_CUR**, set the position from the current position in the file. + * - **GridStore.IO_SEEK_END**, set the position from the end of the file. + * + * @param {Number} [position] the position to seek to + * @param {Number} [seekLocation] seek mode. Use one of the Seek Location modes. + * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. + * @return {null} + * @api public + */ +GridStore.prototype.seek = function(position, seekLocation, callback) { + var self = this; + + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + seekLocation = args.length ? args.shift() : null; + + var seekLocationFinal = seekLocation == null ? exports.GridStore.IO_SEEK_SET : seekLocation; + var finalPosition = position; + var targetPosition = 0; + if(seekLocationFinal == exports.GridStore.IO_SEEK_CUR) { + targetPosition = self.position + finalPosition; + } else if(seekLocationFinal == exports.GridStore.IO_SEEK_END) { + targetPosition = self.length + finalPosition; + } else { + targetPosition = finalPosition; + } + + var newChunkNumber = Math.floor(targetPosition/self.chunkSize); + if(newChunkNumber != self.currentChunk.chunkNumber) { + var seekChunk = function() { + nthChunk(self, newChunkNumber, function(err, chunk) { + self.currentChunk = chunk; + self.position = targetPosition; + self.currentChunk.position = (self.position % self.chunkSize); + callback(null, self); + }); + }; + + if(self.mode[0] == 'w') { + self.currentChunk.save(function(err, chunk) { + seekChunk(); + }); + } else { + seekChunk(); + } + } else { + self.position = targetPosition; + self.currentChunk.position = (self.position % self.chunkSize); + callback(null, self); + } +}; + +/** + * Verify if the file is at EOF. + * + * @return {Boolean} true if the read/write head is at the end of this file. + * @api public + */ +GridStore.prototype.eof = function() { + return this.position == this.length ? true : false; +}; + +/** + * Retrieves a single character from this file. + * + * @param {Function} callback this gets called after this method is executed. Passes null to the first parameter and the character read to the second or null to the second if the read/write head is at the end of the file. + * @return {null} + * @api public + */ +GridStore.prototype.getc = function(callback) { + var self = this; + + if(self.eof()) { + callback(null, null); + } else if(self.currentChunk.eof()) { + nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) { + self.currentChunk = chunk; + self.position = self.position + 1; + callback(null, self.currentChunk.getc()); + }); + } else { + self.position = self.position + 1; + callback(null, self.currentChunk.getc()); + } +}; + +/** + * Writes a string to the file with a newline character appended at the end if + * the given string does not have one. + * + * @param {String} string the string to write. + * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. + * @return {null} + * @api public + */ +GridStore.prototype.puts = function(string, callback) { + var finalString = string.match(/\n$/) == null ? string + "\n" : string; + this.write(finalString, callback); +}; + +/** + * Returns read stream based on this GridStore file + * + * Events + * - **data** {function(item) {}} the data event triggers when a document is ready. + * - **end** {function() {}} the end event triggers when there is no more documents available. + * - **close** {function() {}} the close event triggers when the stream is closed. + * - **error** {function(err) {}} the error event triggers if an error happens. + * + * @param {Boolean} autoclose if true current GridStore will be closed when EOF and 'close' event will be fired + * @return {null} + * @api public + */ +GridStore.prototype.stream = function(autoclose) { + return new ReadStream(autoclose, this); +}; + +/** +* The collection to be used for holding the files and chunks collection. +* +* @classconstant DEFAULT_ROOT_COLLECTION +**/ +GridStore.DEFAULT_ROOT_COLLECTION = 'fs'; + +/** +* Default file mime type +* +* @classconstant DEFAULT_CONTENT_TYPE +**/ +GridStore.DEFAULT_CONTENT_TYPE = 'binary/octet-stream'; + +/** +* Seek mode where the given length is absolute. +* +* @classconstant IO_SEEK_SET +**/ +GridStore.IO_SEEK_SET = 0; + +/** +* Seek mode where the given length is an offset to the current read/write head. +* +* @classconstant IO_SEEK_CUR +**/ +GridStore.IO_SEEK_CUR = 1; + +/** +* Seek mode where the given length is an offset to the end of the file. +* +* @classconstant IO_SEEK_END +**/ +GridStore.IO_SEEK_END = 2; + +/** + * Checks if a file exists in the database. + * + * @param {Db} db the database to query. + * @param {String} name the name of the file to look for. + * @param {String} [rootCollection] the root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. + * @param {Function} callback this will be called after this method executes. Passes null to the first and passes true to the second if the file exists and false otherwise. + * @return {null} + * @api public + */ +GridStore.exist = function(db, fileIdObject, rootCollection, callback) { + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + rootCollection = args.length ? args.shift() : null; + + // Fetch collection + var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION; + db.collection(rootCollectionFinal + ".files", function(err, collection) { + // Build query + var query = (typeof fileIdObject == 'string' || Object.prototype.toString.call(fileIdObject) == '[object RegExp]' ) + ? {'filename':fileIdObject} : {'_id':fileIdObject}; // Attempt to locate file + collection.find(query, function(err, cursor) { + cursor.nextObject(function(err, item) { + callback(null, item == null ? false : true); + }); + }); + }); +}; + +/** + * Gets the list of files stored in the GridFS. + * + * @param {Db} db the database to query. + * @param {String} [rootCollection] the root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. + * @param {Function} callback this will be called after this method executes. Passes null to the first and passes an array of strings containing the names of the files. + * @return {null} + * @api public + */ +GridStore.list = function(db, rootCollection, options, callback) { + var args = Array.prototype.slice.call(arguments, 1); + callback = args.pop(); + rootCollection = args.length ? args.shift() : null; + options = args.length ? args.shift() : {}; + + // Ensure we have correct values + if(rootCollection != null && typeof rootCollection == 'object') { + options = rootCollection; + rootCollection = null; + } + + // Check if we are returning by id not filename + var byId = options['id'] != null ? options['id'] : false; + // Fetch item + var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION; + var items = []; + db.collection((rootCollectionFinal + ".files"), function(err, collection) { + collection.find(function(err, cursor) { + cursor.each(function(err, item) { + if(item != null) { + items.push(byId ? item._id : item.filename); + } else { + callback(null, items); + } + }); + }); + }); +}; + +/** + * Reads the contents of a file. + * + * This method has the following signatures + * + * (db, name, callback) + * (db, name, length, callback) + * (db, name, length, offset, callback) + * (db, name, length, offset, options, callback) + * + * @param {Db} db the database to query. + * @param {String} name the name of the file. + * @param {Number} [length] the size of data to read. + * @param {Number} [offset] the offset from the head of the file of which to start reading from. + * @param {Object} [options] the options for the file. + * @param {Function} callback this will be called after this method executes. A string with an error message will be passed to the first parameter when the length and offset combination exceeds the length of the file while an Error object will be passed if other forms of error occured, otherwise, a string is passed. The second parameter will contain the data read if successful or null if an error occured. + * @return {null} + * @api public + */ +GridStore.read = function(db, name, length, offset, options, callback) { + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + length = args.length ? args.shift() : null; + offset = args.length ? args.shift() : null; + options = args.length ? args.shift() : null; + + new GridStore(db, name, "r", options).open(function(err, gridStore) { + // Make sure we are not reading out of bounds + if(offset && offset >= gridStore.length) return callback("offset larger than size of file", null); + if(length && length > gridStore.length) return callback("length is larger than the size of the file", null); + if(offset && length && (offset + length) > gridStore.length) return callback("offset and length is larger than the size of the file", null); + + if(offset != null) { + gridStore.seek(offset, function(err, gridStore) { + gridStore.read(length, function(err, data) { + callback(err, data); + }); + }); + } else { + gridStore.read(length, function(err, data) { + callback(err, data); + }); + } + }); +}; + +/** + * Reads the data of this file. + * + * @param {Db} db the database to query. + * @param {String} name the name of the file. + * @param {String} [separator] the character to be recognized as the newline separator. + * @param {Object} [options] file options. + * @param {Function} callback this will be called after this method is executed. The first parameter will be null and the second parameter will contain an array of strings representing the entire data, each element representing a line including the separator character. + * @return {null} + * @api public + */ +GridStore.readlines = function(db, name, separator, options, callback) { + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + separator = args.length ? args.shift() : null; + options = args.length ? args.shift() : null; + + var finalSeperator = separator == null ? "\n" : separator; + new GridStore(db, name, "r", options).open(function(err, gridStore) { + gridStore.readlines(finalSeperator, function(err, lines) { + callback(err, lines); + }); + }); +}; + +/** + * Deletes the chunks and metadata information of a file from GridFS. + * + * @param {Db} db the database to interact with. + * @param {String|Array} names the name/names of the files to delete. + * @param {Object} [options] the options for the files. + * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. + * @return {null} + * @api public + */ +GridStore.unlink = function(db, names, options, callback) { + var self = this; + var args = Array.prototype.slice.call(arguments, 2); + callback = args.pop(); + options = args.length ? args.shift() : null; + + if(names.constructor == Array) { + var tc = 0; + for(var i = 0; i < names.length; i++) { + ++tc; + self.unlink(db, names[i], function(result) { + if(--tc == 0) { + callback(null, self); + } + }); + } + } else { + new GridStore(db, names, "w", options).open(function(err, gridStore) { + deleteChunks(gridStore, function(err, result) { + gridStore.collection(function(err, collection) { + collection.remove({'_id':gridStore.fileId}, {safe:true}, function(err, collection) { + callback(err, self); + }); + }); + }); + }); + } +}; + +/** + * @ignore + * @api private + */ +exports.GridStore = GridStore; diff --git a/node_modules/mongodb/lib/mongodb/gridfs/readstream.js b/node_modules/mongodb/lib/mongodb/gridfs/readstream.js new file mode 100644 index 0000000..2d3e574 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/gridfs/readstream.js @@ -0,0 +1,179 @@ +var Stream = require('stream').Stream, + util = require('util'); + +/** + * ReadStream + * + * Returns a stream interface for the **file**. + * + * Events + * - **data** {function(item) {}} the data event triggers when a document is ready. + * - **end** {function() {}} the end event triggers when there is no more documents available. + * - **close** {function() {}} the close event triggers when the stream is closed. + * - **error** {function(err) {}} the error event triggers if an error happens. + * + * @class Represents a GridFS File Stream. + * @param {Boolean} autoclose automatically close file when the stream reaches the end. + * @param {GridStore} cursor a cursor object that the stream wraps. + * @return {ReadStream} + */ +function ReadStream(autoclose, gstore) { + if (!(this instanceof ReadStream)) return new ReadStream(autoclose, gstore); + Stream.call(this); + + this.autoclose = !!autoclose; + this.gstore = gstore; + + this.finalLength = gstore.length - gstore.position; + this.completedLength = 0; + + this.paused = false; + this.readable = true; + this.pendingChunk = null; + this.executing = false; + + var self = this; + process.nextTick(function() { + self._execute(); + }); +}; + +/** + * Inherit from Stream + * @ignore + * @api private + */ +ReadStream.prototype.__proto__ = Stream.prototype; + +/** + * Flag stating whether or not this stream is readable. + */ +ReadStream.prototype.readable; + +/** + * Flag stating whether or not this stream is paused. + */ +ReadStream.prototype.paused; + +/** + * @ignore + * @api private + */ +ReadStream.prototype._execute = function() { + if(this.paused === true || this.readable === false) { + return; + } + + var gstore = this.gstore; + var self = this; + // Set that we are executing + this.executing = true; + + var last = false; + var toRead = 0; + + if ((gstore.currentChunk.length() - gstore.currentChunk.position + 1 + self.completedLength) >= self.finalLength) { + toRead = self.finalLength - self.completedLength; + self.executing = false; + last = true; + } else { + toRead = gstore.currentChunk.length(); + } + + var data = gstore.currentChunk.readSlice(toRead); + + if(data != null) { + self.completedLength += data.length; + self.pendingChunk = null; + self.emit("data", data); + } + + if(last === true) { + self.readable = false; + self.emit("end"); + + if(self.autoclose === true) { + if(gstore.mode[0] == "w") { + gstore.close(function(err, doc) { + if (err) { + self.emit("error", err); + return; + } + self.readable = false; + self.emit("close", doc); + }); + } else { + self.readable = false; + self.emit("close"); + } + } + } else { + gstore._nthChunk(gstore.currentChunk.chunkNumber + 1, function(err, chunk) { + if(err) { + self.readable = false; + self.emit("error", err); + self.executing = false; + return; + } + + self.pendingChunk = chunk; + if(self.paused === true) { + self.executing = false; + return; + } + + gstore.currentChunk = self.pendingChunk; + self._execute(); + }); + } +}; + +/** + * Pauses this stream, then no farther events will be fired. + * + * @ignore + * @api public + */ +ReadStream.prototype.pause = function() { + if(!this.executing) { + this.paused = true; + } +}; + +/** + * Destroys the stream, then no farther events will be fired. + * + * @ignore + * @api public + */ +ReadStream.prototype.destroy = function() { + this.readable = false; + // Emit close event + this.emit("close"); +}; + +/** + * Resumes this stream. + * + * @ignore + * @api public + */ +ReadStream.prototype.resume = function() { + if(this.paused === false || !this.readable) { + return; + } + + this.paused = false; + var self = this; + if(self.pendingChunk != null) { + self.currentChunk = self.pendingChunk; + process.nextTick(function() { + self._execute(); + }); + } else { + self.readable = false; + self.emit("close"); + } +}; + +exports.ReadStream = ReadStream; diff --git a/node_modules/mongodb/lib/mongodb/index.js b/node_modules/mongodb/lib/mongodb/index.js new file mode 100644 index 0000000..721e2f3 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/index.js @@ -0,0 +1,142 @@ +try { + exports.BSONPure = require('bson').BSONPure; + exports.BSONNative = require('bson').BSONNative; +} catch(err) { + // do nothing +} + +[ 'commands/base_command' + , 'commands/db_command' + , 'commands/delete_command' + , 'commands/get_more_command' + , 'commands/insert_command' + , 'commands/kill_cursor_command' + , 'commands/query_command' + , 'commands/update_command' + , 'responses/mongo_reply' + , 'admin' + , 'collection' + , 'connection/connection' + , 'connection/server' + , 'connection/repl_set_servers' + , 'cursor' + , 'db' + , 'gridfs/grid' + , 'gridfs/chunk' + , 'gridfs/gridstore'].forEach(function (path) { + var module = require('./' + path); + for (var i in module) { + exports[i] = module[i]; + } + + // Add BSON Classes + exports.Binary = require('bson').Binary; + exports.Code = require('bson').Code; + exports.DBRef = require('bson').DBRef; + exports.Double = require('bson').Double; + exports.Long = require('bson').Long; + exports.MinKey = require('bson').MinKey; + exports.MaxKey = require('bson').MaxKey; + exports.ObjectID = require('bson').ObjectID; + exports.Symbol = require('bson').Symbol; + exports.Timestamp = require('bson').Timestamp; + + // Add BSON Parser + exports.BSON = require('bson').BSONPure.BSON; +}); + +// Exports all the classes for the PURE JS BSON Parser +exports.pure = function() { + var classes = {}; + // Map all the classes + [ 'commands/base_command' + , 'commands/db_command' + , 'commands/delete_command' + , 'commands/get_more_command' + , 'commands/insert_command' + , 'commands/kill_cursor_command' + , 'commands/query_command' + , 'commands/update_command' + , 'responses/mongo_reply' + , 'admin' + , 'collection' + , 'connection/connection' + , 'connection/server' + , 'connection/repl_set_servers' + , 'cursor' + , 'db' + , 'gridfs/grid' + , 'gridfs/chunk' + , 'gridfs/gridstore'].forEach(function (path) { + var module = require('./' + path); + for (var i in module) { + classes[i] = module[i]; + } + }); + + // Add BSON Classes + classes.Binary = require('bson').Binary; + classes.Code = require('bson').Code; + classes.DBRef = require('bson').DBRef; + classes.Double = require('bson').Double; + classes.Long = require('bson').Long; + classes.MinKey = require('bson').MinKey; + classes.MaxKey = require('bson').MaxKey; + classes.ObjectID = require('bson').ObjectID; + classes.Symbol = require('bson').Symbol; + classes.Timestamp = require('bson').Timestamp; + + // Add BSON Parser + classes.BSON = require('bson').BSONPure.BSON; + + // Return classes list + return classes; +} + +// Exports all the classes for the PURE JS BSON Parser +exports.native = function() { + var classes = {}; + // Map all the classes + [ 'commands/base_command' + , 'commands/db_command' + , 'commands/delete_command' + , 'commands/get_more_command' + , 'commands/insert_command' + , 'commands/kill_cursor_command' + , 'commands/query_command' + , 'commands/update_command' + , 'responses/mongo_reply' + , 'admin' + , 'collection' + , 'connection/connection' + , 'connection/server' + , 'connection/repl_set_servers' + , 'cursor' + , 'db' + , 'gridfs/grid' + , 'gridfs/chunk' + , 'gridfs/gridstore'].forEach(function (path) { + var module = require('./' + path); + for (var i in module) { + classes[i] = module[i]; + } + }); + + // Add BSON Classes + classes.Binary = require('bson').Binary; + classes.Code = require('bson').Code; + classes.DBRef = require('bson').DBRef; + classes.Double = require('bson').Double; + classes.Long = require('bson').Long; + classes.MinKey = require('bson').MinKey; + classes.MaxKey = require('bson').MaxKey; + classes.ObjectID = require('bson').ObjectID; + classes.Symbol = require('bson').Symbol; + classes.Timestamp = require('bson').Timestamp; + + // Add BSON Parser + classes.BSON = require('bson').BSONNative.BSON; + + // Return classes list + return classes; +} diff --git a/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js b/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js new file mode 100644 index 0000000..74396fa --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js @@ -0,0 +1,131 @@ +var Long = require('bson').Long; + +/** + Reply message from mongo db +**/ +var MongoReply = exports.MongoReply = function() { + this.documents = []; + this.index = 0; +}; + +MongoReply.prototype.parseHeader = function(binary_reply, bson) { + // Unpack the standard header first + this.messageLength = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + this.index = this.index + 4; + // Fetch the request id for this reply + this.requestId = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + this.index = this.index + 4; + // Fetch the id of the request that triggered the response + this.responseTo = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + // Skip op-code field + this.index = this.index + 4 + 4; + // Unpack the reply message + this.responseFlag = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + this.index = this.index + 4; + // Unpack the cursor id (a 64 bit long integer) + var low_bits = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + this.index = this.index + 4; + var high_bits = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + this.index = this.index + 4; + this.cursorId = new Long(low_bits, high_bits); + // Unpack the starting from + this.startingFrom = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + this.index = this.index + 4; + // Unpack the number of objects returned + this.numberReturned = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + this.index = this.index + 4; +} + +MongoReply.prototype.parseBody = function(binary_reply, bson, raw, callback) { + raw = raw == null ? false : raw; + // Just set a doc limit for deserializing + var docLimitSize = 1024*20; + + // If our message length is very long, let's switch to process.nextTick for messages + if(this.messageLength > docLimitSize) { + var batchSize = this.numberReturned; + this.documents = new Array(this.numberReturned); + + // Just walk down until we get a positive number >= 1 + for(var i = 50; i > 0; i--) { + if((this.numberReturned/i) >= 1) { + batchSize = i; + break; + } + } + + // Actual main creator of the processFunction setting internal state to control the flow + var parseFunction = function(_self, _binary_reply, _batchSize, _numberReturned) { + var object_index = 0; + // Internal loop process that will use nextTick to ensure we yield some time + var processFunction = function() { + // Adjust batchSize if we have less results left than batchsize + if((_numberReturned - object_index) < _batchSize) { + _batchSize = _numberReturned - object_index; + } + + // If raw just process the entries + if(raw) { + // Iterate over the batch + for(var i = 0; i < _batchSize; i++) { + // Are we done ? + if(object_index <= _numberReturned) { + // Read the size of the bson object + var bsonObjectSize = _binary_reply[_self.index] | _binary_reply[_self.index + 1] << 8 | _binary_reply[_self.index + 2] << 16 | _binary_reply[_self.index + 3] << 24; + // If we are storing the raw responses to pipe straight through + _self.documents[object_index] = binary_reply.slice(_self.index, _self.index + bsonObjectSize); + // Adjust binary index to point to next block of binary bson data + _self.index = _self.index + bsonObjectSize; + // Update number of docs parsed + object_index = object_index + 1; + } + } + } else { + // Parse documents + _self.index = bson.deserializeStream(binary_reply, _self.index, _batchSize, _self.documents, object_index); + // Adjust index + object_index = object_index + _batchSize; + } + + // If we hav more documents process NextTick + if(object_index < _numberReturned) { + process.nextTick(processFunction); + } else { + callback(null); + } + } + + // Return the process function + return processFunction; + }(this, binary_reply, batchSize, this.numberReturned)(); + } else { + // Let's unpack all the bson documents, deserialize them and store them + for(var object_index = 0; object_index < this.numberReturned; object_index++) { + // Read the size of the bson object + var bsonObjectSize = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; + // If we are storing the raw responses to pipe straight through + if(raw) { + // Deserialize the object and add to the documents array + this.documents.push(binary_reply.slice(this.index, this.index + bsonObjectSize)); + } else { + // Deserialize the object and add to the documents array + this.documents.push(bson.deserialize(binary_reply.slice(this.index, this.index + bsonObjectSize))); + } + // Adjust binary index to point to next block of binary bson data + this.index = this.index + bsonObjectSize; + } + + callback(null); + } +} + +MongoReply.prototype.is_error = function(){ + if(this.documents.length == 1) { + return this.documents[0].ok == 1 ? false : true; + } + return false; +}; + +MongoReply.prototype.error_message = function() { + return this.documents.length == 1 && this.documents[0].ok == 1 ? '' : this.documents[0].errmsg; +}; \ No newline at end of file diff --git a/node_modules/mongodb/lib/mongodb/utils.js b/node_modules/mongodb/lib/mongodb/utils.js new file mode 100644 index 0000000..07a0ab0 --- /dev/null +++ b/node_modules/mongodb/lib/mongodb/utils.js @@ -0,0 +1,74 @@ +/** + * Sort functions, Normalize and prepare sort parameters + */ +var formatSortValue = exports.formatSortValue = function(sortDirection) { + var value = ("" + sortDirection).toLowerCase(); + + switch (value) { + case 'ascending': + case 'asc': + case '1': + return 1; + case 'descending': + case 'desc': + case '-1': + return -1; + default: + throw new Error("Illegal sort clause, must be of the form " + + "[['field1', '(ascending|descending)'], " + + "['field2', '(ascending|descending)']]"); + } +}; + +var formattedOrderClause = exports.formattedOrderClause = function(sortValue) { + var orderBy = {}; + + if (Array.isArray(sortValue)) { + for(var i = 0; i < sortValue.length; i++) { + if(sortValue[i].constructor == String) { + orderBy[sortValue[i]] = 1; + } else { + orderBy[sortValue[i][0]] = formatSortValue(sortValue[i][1]); + } + } + } else if(Object.prototype.toString.call(sortValue) === '[object Object]') { + orderBy = sortValue; + } else if (sortValue.constructor == String) { + orderBy[sortValue] = 1; + } else { + throw new Error("Illegal sort clause, must be of the form " + + "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"); + } + + return orderBy; +}; + +exports.encodeInt = function(value) { + var buffer = new Buffer(4); + buffer[3] = (value >> 24) & 0xff; + buffer[2] = (value >> 16) & 0xff; + buffer[1] = (value >> 8) & 0xff; + buffer[0] = value & 0xff; + return buffer; +} + +exports.encodeIntInPlace = function(value, buffer, index) { + buffer[index + 3] = (value >> 24) & 0xff; + buffer[index + 2] = (value >> 16) & 0xff; + buffer[index + 1] = (value >> 8) & 0xff; + buffer[index] = value & 0xff; +} + +exports.encodeCString = function(string) { + var buf = new Buffer(string, 'utf8'); + return [buf, new Buffer([0])]; +} + +exports.decodeUInt32 = function(array, index) { + return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24; +} + +// Decode the int +exports.decodeUInt8 = function(array, index) { + return array[index]; +} diff --git a/node_modules/mongodb/node_modules/bson/.travis.yml b/node_modules/mongodb/node_modules/bson/.travis.yml new file mode 100644 index 0000000..90b208a --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - 0.4 + - 0.6 + - 0.7 # development version of 0.8, may be unstable \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/Makefile b/node_modules/mongodb/node_modules/bson/Makefile new file mode 100644 index 0000000..88b1ddc --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/Makefile @@ -0,0 +1,31 @@ +NODE = node +NPM = npm +NODEUNIT = node_modules/nodeunit/bin/nodeunit +name = all + +total: build_native + +test: build_native + $(NODEUNIT) ./test/node + TEST_NATIVE=TRUE $(NODEUNIT) ./test/node + +build_native: + $(MAKE) -C ./ext all + +build_native_debug: + $(MAKE) -C ./ext all_debug + +build_native_clang: + $(MAKE) -C ./ext clang + +build_native_clang_debug: + $(MAKE) -C ./ext clang_debug + +clean_native: + $(MAKE) -C ./ext clean + +clean: + rm ./ext/bson.node + rm -r ./ext/build + +.PHONY: total diff --git a/node_modules/mongodb/node_modules/bson/README b/node_modules/mongodb/node_modules/bson/README new file mode 100644 index 0000000..e69de29 diff --git a/node_modules/mongodb/node_modules/bson/ext/Makefile b/node_modules/mongodb/node_modules/bson/ext/Makefile new file mode 100644 index 0000000..435999e --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/ext/Makefile @@ -0,0 +1,28 @@ +NODE = node +name = all +JOBS = 1 + +all: + rm -rf build .lock-wscript bson.node + node-waf configure build + cp -R ./build/Release/bson.node . || true + +all_debug: + rm -rf build .lock-wscript bson.node + node-waf --debug configure build + cp -R ./build/Release/bson.node . || true + +clang: + rm -rf build .lock-wscript bson.node + CXX=clang node-waf configure build + cp -R ./build/Release/bson.node . || true + +clang_debug: + rm -rf build .lock-wscript bson.node + CXX=clang node-waf --debug configure build + cp -R ./build/Release/bson.node . || true + +clean: + rm -rf build .lock-wscript bson.node + +.PHONY: all \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/ext/bson.cc b/node_modules/mongodb/node_modules/bson/ext/bson.cc new file mode 100644 index 0000000..8906eea --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/ext/bson.cc @@ -0,0 +1,2165 @@ +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bson.h" + +using namespace v8; +using namespace node; +using namespace std; + +// BSON DATA TYPES +const uint32_t BSON_DATA_NUMBER = 1; +const uint32_t BSON_DATA_STRING = 2; +const uint32_t BSON_DATA_OBJECT = 3; +const uint32_t BSON_DATA_ARRAY = 4; +const uint32_t BSON_DATA_BINARY = 5; +const uint32_t BSON_DATA_OID = 7; +const uint32_t BSON_DATA_BOOLEAN = 8; +const uint32_t BSON_DATA_DATE = 9; +const uint32_t BSON_DATA_NULL = 10; +const uint32_t BSON_DATA_REGEXP = 11; +const uint32_t BSON_DATA_CODE = 13; +const uint32_t BSON_DATA_SYMBOL = 14; +const uint32_t BSON_DATA_CODE_W_SCOPE = 15; +const uint32_t BSON_DATA_INT = 16; +const uint32_t BSON_DATA_TIMESTAMP = 17; +const uint32_t BSON_DATA_LONG = 18; +const uint32_t BSON_DATA_MIN_KEY = 0xff; +const uint32_t BSON_DATA_MAX_KEY = 0x7f; + +const int32_t BSON_INT32_MAX = (int32_t)2147483647L; +const int32_t BSON_INT32_MIN = (int32_t)(-1) * 2147483648L; + +const int64_t BSON_INT64_MAX = ((int64_t)1 << 63) - 1; +const int64_t BSON_INT64_MIN = (int64_t)-1 << 63; + +const int64_t JS_INT_MAX = (int64_t)1 << 53; +const int64_t JS_INT_MIN = (int64_t)-1 << 53; + +static Handle VException(const char *msg) { + HandleScope scope; + return ThrowException(Exception::Error(String::New(msg))); + }; + +Persistent BSON::constructor_template; + +void BSON::Initialize(v8::Handle target) { + // Grab the scope of the call from Node + HandleScope scope; + // Define a new function template + Local t = FunctionTemplate::New(New); + constructor_template = Persistent::New(t); + constructor_template->InstanceTemplate()->SetInternalFieldCount(1); + constructor_template->SetClassName(String::NewSymbol("BSON")); + + // Instance methods + NODE_SET_PROTOTYPE_METHOD(constructor_template, "calculateObjectSize", CalculateObjectSize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "serialize", BSONSerialize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "serializeWithBufferAndIndex", SerializeWithBufferAndIndex); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserialize", BSONDeserialize); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserializeStream", BSONDeserializeStream); + + // Experimental + // NODE_SET_PROTOTYPE_METHOD(constructor_template, "calculateObjectSize2", CalculateObjectSize2); + // NODE_SET_PROTOTYPE_METHOD(constructor_template, "serialize2", BSONSerialize2); + // NODE_SET_METHOD(constructor_template->GetFunction(), "serialize2", BSONSerialize2); + + target->ForceSet(String::NewSymbol("BSON"), constructor_template->GetFunction()); +} + +// Create a new instance of BSON and assing it the existing context +Handle BSON::New(const Arguments &args) { + HandleScope scope; + + // Check that we have an array + if(args.Length() == 1 && args[0]->IsArray()) { + // Cast the array to a local reference + Local array = Local::Cast(args[0]); + + if(array->Length() > 0) { + // Create a bson object instance and return it + BSON *bson = new BSON(); + + // Setup pre-allocated comparision objects + bson->_bsontypeString = Persistent::New(String::New("_bsontype")); + bson->_longLowString = Persistent::New(String::New("low_")); + bson->_longHighString = Persistent::New(String::New("high_")); + bson->_objectIDidString = Persistent::New(String::New("id")); + bson->_binaryPositionString = Persistent::New(String::New("position")); + bson->_binarySubTypeString = Persistent::New(String::New("sub_type")); + bson->_binaryBufferString = Persistent::New(String::New("buffer")); + bson->_doubleValueString = Persistent::New(String::New("value")); + bson->_symbolValueString = Persistent::New(String::New("value")); + bson->_dbRefRefString = Persistent::New(String::New("$ref")); + bson->_dbRefIdRefString = Persistent::New(String::New("$id")); + bson->_dbRefDbRefString = Persistent::New(String::New("$db")); + bson->_dbRefNamespaceString = Persistent::New(String::New("namespace")); + bson->_dbRefDbString = Persistent::New(String::New("db")); + bson->_dbRefOidString = Persistent::New(String::New("oid")); + + // total number of found classes + uint32_t numberOfClasses = 0; + + // Iterate over all entries to save the instantiate funtions + for(uint32_t i = 0; i < array->Length(); i++) { + // Let's get a reference to the function + Local func = Local::Cast(array->Get(i)); + Local functionName = func->GetName()->ToString(); + + // Save the functions making them persistant handles (they don't get collected) + if(functionName->StrictEquals(String::New("Long"))) { + bson->longConstructor = Persistent::New(func); + bson->longString = Persistent::New(String::New("Long")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("ObjectID"))) { + bson->objectIDConstructor = Persistent::New(func); + bson->objectIDString = Persistent::New(String::New("ObjectID")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Binary"))) { + bson->binaryConstructor = Persistent::New(func); + bson->binaryString = Persistent::New(String::New("Binary")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Code"))) { + bson->codeConstructor = Persistent::New(func); + bson->codeString = Persistent::New(String::New("Code")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("DBRef"))) { + bson->dbrefConstructor = Persistent::New(func); + bson->dbrefString = Persistent::New(String::New("DBRef")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Symbol"))) { + bson->symbolConstructor = Persistent::New(func); + bson->symbolString = Persistent::New(String::New("Symbol")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Double"))) { + bson->doubleConstructor = Persistent::New(func); + bson->doubleString = Persistent::New(String::New("Double")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("Timestamp"))) { + bson->timestampConstructor = Persistent::New(func); + bson->timestampString = Persistent::New(String::New("Timestamp")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("MinKey"))) { + bson->minKeyConstructor = Persistent::New(func); + bson->minKeyString = Persistent::New(String::New("MinKey")); + numberOfClasses = numberOfClasses + 1; + } else if(functionName->StrictEquals(String::New("MaxKey"))) { + bson->maxKeyConstructor = Persistent::New(func); + bson->maxKeyString = Persistent::New(String::New("MaxKey")); + numberOfClasses = numberOfClasses + 1; + } + } + + // Check if we have the right number of constructors otherwise throw an error + if(numberOfClasses != 10) { + // Destroy object + delete(bson); + // Fire exception + return VException("Missing function constructor for either [Long/ObjectID/Binary/Code/DbRef/Symbol/Double/Timestamp/MinKey/MaxKey]"); + } else { + bson->Wrap(args.This()); + return args.This(); + } + } else { + return VException("No types passed in"); + } + } else { + return VException("Argument passed in must be an array of types"); + } +} + +void BSON::write_int32(char *data, uint32_t value) { + // Write the int to the char* + memcpy(data, &value, 4); +} + +void BSON::write_double(char *data, double value) { + // Write the double to the char* + memcpy(data, &value, 8); +} + +void BSON::write_int64(char *data, int64_t value) { + // Write the int to the char* + memcpy(data, &value, 8); +} + +char *BSON::check_key(Local key) { + // Allocate space for they key string + char *key_str = (char *)malloc(key->Utf8Length() * sizeof(char) + 1); + // Error string + char *error_str = (char *)malloc(256 * sizeof(char)); + // Decode the key + ssize_t len = DecodeBytes(key, BINARY); + DecodeWrite(key_str, len, key, BINARY); + *(key_str + key->Utf8Length()) = '\0'; + // Check if we have a valid key + if(key->Utf8Length() > 0 && *(key_str) == '$') { + // Create the string + sprintf(error_str, "key %s must not start with '$'", key_str); + // Free up memory + free(key_str); + // Throw exception with string + throw error_str; + } else if(key->Utf8Length() > 0 && strchr(key_str, '.') != NULL) { + // Create the string + sprintf(error_str, "key %s must not contain '.'", key_str); + // Free up memory + free(key_str); + // Throw exception with string + throw error_str; + } + // Free allocated space + free(key_str); + free(error_str); + // Return No check key error + return NULL; +} + +const char* BSON::ToCString(const v8::String::Utf8Value& value) { + return *value ? *value : ""; +} + +Handle BSON::decodeDBref(BSON *bson, Local ref, Local oid, Local db) { + HandleScope scope; + Local argv[] = {ref, oid, db}; + Handle dbrefObj = bson->dbrefConstructor->NewInstance(3, argv); + return scope.Close(dbrefObj); +} + +Handle BSON::decodeCode(BSON *bson, char *code, Handle scope_object) { + HandleScope scope; + + Local argv[] = {String::New(code), scope_object->ToObject()}; + Handle codeObj = bson->codeConstructor->NewInstance(2, argv); + return scope.Close(codeObj); +} + +Handle BSON::decodeBinary(BSON *bson, uint32_t sub_type, uint32_t number_of_bytes, char *data) { + HandleScope scope; + + // Create a buffer object that wraps the raw stream + Buffer *bufferObj = Buffer::New(data, number_of_bytes); + // Arguments to be passed to create the binary + Handle argv[] = {bufferObj->handle_, Uint32::New(sub_type)}; + // Return the buffer handle + Local bufferObjHandle = bson->binaryConstructor->NewInstance(2, argv); + // Close the scope + return scope.Close(bufferObjHandle); +} + +Handle BSON::decodeOid(BSON *bson, char *oid) { + HandleScope scope; + + // Encode the string (string - null termiating character) + Local bin_value = Encode(oid, 12, BINARY)->ToString(); + + // Return the id object + Local argv[] = {bin_value}; + Local oidObj = bson->objectIDConstructor->NewInstance(1, argv); + return scope.Close(oidObj); +} + +Handle BSON::decodeLong(BSON *bson, char *data, uint32_t index) { + HandleScope scope; + + // Decode the integer value + int32_t lowBits = 0; + int32_t highBits = 0; + memcpy(&lowBits, (data + index), 4); + memcpy(&highBits, (data + index + 4), 4); + + // Decode 64bit value + int64_t value = 0; + memcpy(&value, (data + index), 8); + + // If value is < 2^53 and >-2^53 + if((highBits < 0x200000 || (highBits == 0x200000 && lowBits == 0)) && highBits >= -0x200000) { + int64_t finalValue = 0; + memcpy(&finalValue, (data + index), 8); + return scope.Close(Number::New(finalValue)); + } + + // Instantiate the js object and pass it back + Local argv[] = {Int32::New(lowBits), Int32::New(highBits)}; + Local longObject = bson->longConstructor->NewInstance(2, argv); + return scope.Close(longObject); +} + +Handle BSON::decodeTimestamp(BSON *bson, char *data, uint32_t index) { + HandleScope scope; + + // Decode the integer value + int32_t lowBits = 0; + int32_t highBits = 0; + memcpy(&lowBits, (data + index), 4); + memcpy(&highBits, (data + index + 4), 4); + + // Build timestamp + Local argv[] = {Int32::New(lowBits), Int32::New(highBits)}; + Handle timestamp_obj = bson->timestampConstructor->NewInstance(2, argv); + return scope.Close(timestamp_obj); +} + +// Search for 0 terminated C string and return the string +char* BSON::extract_string(char *data, uint32_t offset) { + char *prt = strchr((data + offset), '\0'); + if(prt == NULL) return NULL; + // Figure out the length of the string + uint32_t length = (prt - data) - offset; + // Allocate memory for the new string + char *string_name = (char *)malloc((length * sizeof(char)) + 1); + // Copy the variable into the string_name + strncpy(string_name, (data + offset), length); + // Ensure the string is null terminated + *(string_name + length) = '\0'; + // Return the unpacked string + return string_name; +} + +// Decode a byte +uint16_t BSON::deserialize_int8(char *data, uint32_t offset) { + uint16_t value = 0; + value |= *(data + offset + 0); + return value; +} + +// Requires a 4 byte char array +uint32_t BSON::deserialize_int32(char* data, uint32_t offset) { + uint32_t value = 0; + memcpy(&value, (data + offset), 4); + return value; +} + +//------------------------------------------------------------------------------------------------ +// +// Experimental +// +//------------------------------------------------------------------------------------------------ +Handle BSON::CalculateObjectSize2(const Arguments &args) { + HandleScope scope; + // Ensure we have a valid object + if(args.Length() == 1 && !args[0]->IsObject()) return VException("One argument required - [object]"); + if(args.Length() > 1) return VException("One argument required - [object]"); + // Calculate size of the object + uint32_t object_size = BSON::calculate_object_size2(args[0]); + // Return the object size + return scope.Close(Uint32::New(object_size)); +} + +uint32_t BSON::calculate_object_size2(Handle value) { + // Final object size + uint32_t object_size = (4 + 1); + uint32_t stackIndex = 0; + // Controls the flow + bool done = false; + bool finished = false; + + // Current object we are processing + Local currentObject = value->ToObject(); + + // Current list of object keys + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + Local keys = currentObject->GetPropertyNames(); + #else + Local keys = currentObject->GetOwnPropertyNames(); + #endif + + // Contains pointer to keysIndex + uint32_t keysIndex = 0; + uint32_t keysLength = keys->Length(); + + // printf("=================================================================================\n"); + // printf("Start serializing\n"); + + while(!done) { + // If the index is bigger than the number of keys for the object + // we finished up the previous object and are ready for the next one + if(keysIndex >= keysLength) { + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + keys = currentObject->GetPropertyNames(); + #else + keys = currentObject->GetOwnPropertyNames(); + #endif + keysLength = keys->Length(); + } + + // Iterate over all the keys + while(keysIndex < keysLength) { + // Fetch the key name + Local name = keys->Get(keysIndex++)->ToString(); + // Fetch the object related to the key + Local value = currentObject->Get(name); + // Add size of the name, plus zero, plus type + object_size += name->Utf8Length() + 1 + 1; + + // If we have a string + if(value->IsString()) { + object_size += value->ToString()->Utf8Length() + 1 + 4; + } else if(value->IsNumber()) { + // Check if we have a float value or a long value + Local number = value->ToNumber(); + double d_number = number->NumberValue(); + int64_t l_number = number->IntegerValue(); + // Check if we have a double value and not a int64 + double d_result = d_number - l_number; + // If we have a value after subtracting the integer value we have a float + if(d_result > 0 || d_result < 0) { + object_size = object_size + 8; + } else if(l_number <= BSON_INT32_MAX && l_number >= BSON_INT32_MIN) { + object_size = object_size + 4; + } else { + object_size = object_size + 8; + } + } else if(value->IsBoolean()) { + object_size = object_size + 1; + } else if(value->IsDate()) { + object_size = object_size + 8; + } else if(value->IsRegExp()) { + // Fetch the string for the regexp + Handle regExp = Handle::Cast(value); + ssize_t len = DecodeBytes(regExp->GetSource(), UTF8); + int flags = regExp->GetFlags(); + + // global + if((flags & (1 << 0)) != 0) len++; + // ignorecase + if((flags & (1 << 1)) != 0) len++; + //multiline + if((flags & (1 << 2)) != 0) len++; + // if((flags & (1 << 2)) != 0) len++; + // Calculate the space needed for the regexp: size of string - 2 for the /'ses +2 for null termiations + object_size = object_size + len + 2; + } else if(value->IsNull() || value->IsUndefined()) { + } + // } else if(value->IsNumber()) { + // // Check if we have a float value or a long value + // Local number = value->ToNumber(); + // double d_number = number->NumberValue(); + // int64_t l_number = number->IntegerValue(); + // // Check if we have a double value and not a int64 + // double d_result = d_number - l_number; + // // If we have a value after subtracting the integer value we have a float + // if(d_result > 0 || d_result < 0) { + // object_size = name->Utf8Length() + 1 + object_size + 8 + 1; + // } else if(l_number <= BSON_INT32_MAX && l_number >= BSON_INT32_MIN) { + // object_size = name->Utf8Length() + 1 + object_size + 4 + 1; + // } else { + // object_size = name->Utf8Length() + 1 + object_size + 8 + 1; + // } + // } else if(value->IsObject()) { + // printf("------------- hello\n"); + // } + } + + // If we have finished all the keys + if(keysIndex == keysLength) { + finished = false; + } + + // Validate the stack + if(stackIndex == 0) { + // printf("======================================================================== 3\n"); + done = true; + } else if(finished || keysIndex == keysLength) { + // Pop off the stack + stackIndex = stackIndex - 1; + // Fetch the current object stack + // vector > currentObjectStored = stack.back(); + // stack.pop_back(); + // // Unroll the current object + // currentObject = currentObjectStored.back()->ToObject(); + // currentObjectStored.pop_back(); + // // Unroll the keysIndex + // keys = Local::Cast(currentObjectStored.back()->ToObject()); + // currentObjectStored.pop_back(); + // // Unroll the keysIndex + // keysIndex = currentObjectStored.back()->ToUint32()->Value(); + // currentObjectStored.pop_back(); + // // Check if we finished up + // if(keysIndex == keys->Length()) { + // finished = true; + // } + } + } + + return object_size; +} + +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ +Handle BSON::BSONDeserialize(const Arguments &args) { + HandleScope scope; + + // Ensure that we have an parameter + if(Buffer::HasInstance(args[0]) && args.Length() > 1) return VException("One argument required - buffer1."); + if(args[0]->IsString() && args.Length() > 1) return VException("One argument required - string1."); + // Throw an exception if the argument is not of type Buffer + if(!Buffer::HasInstance(args[0]) && !args[0]->IsString()) return VException("Argument must be a Buffer or String."); + + // Define pointer to data + char *data; + Local obj = args[0]->ToObject(); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // If we passed in a buffer, let's unpack it, otherwise let's unpack the string + if(Buffer::HasInstance(obj)) { + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(obj); + data = buffer->data(); + uint32_t length = buffer->length(); + #else + data = Buffer::Data(obj); + uint32_t length = Buffer::Length(obj); + #endif + + // Validate that we have at least 5 bytes + if(length < 5) { + return VException("corrupt bson message < 5 bytes long"); + } + + // Deserialize the data + return BSON::deserialize(bson, data, length, 0, NULL); + } else { + // The length of the data for this encoding + ssize_t len = DecodeBytes(args[0], BINARY); + + // Validate that we have at least 5 bytes + if(len < 5) { + return VException("corrupt bson message < 5 bytes long"); + } + + // Let's define the buffer size + data = (char *)malloc(len); + // Write the data to the buffer from the string object + ssize_t written = DecodeWrite(data, len, args[0], BINARY); + // Assert that we wrote the same number of bytes as we have length + assert(written == len); + // Get result + Handle result = BSON::deserialize(bson, data, len, 0, NULL); + // Free memory + free(data); + // Deserialize the content + return result; + } +} + +// Deserialize the stream +Handle BSON::deserialize(BSON *bson, char *data, uint32_t inDataLength, uint32_t startIndex, bool is_array_item) { + HandleScope scope; + // Holds references to the objects that are going to be returned + Local return_data = Object::New(); + Local return_array = Array::New(); + // The current index in the char data + uint32_t index = startIndex; + // Decode the size of the BSON data structure + uint32_t size = BSON::deserialize_int32(data, index); + + // If we have an illegal message size + if(size > inDataLength) return VException("corrupt bson message"); + + // Data length + uint32_t dataLength = index + size; + + // Adjust the index to point to next piece + index = index + 4; + + // While we have data left let's decode + while(index < dataLength) { + // Read the first to bytes to indicate the type of object we are decoding + uint8_t type = BSON::deserialize_int8(data, index); + // Adjust index to skip type byte + index = index + 1; + + if(type == BSON_DATA_STRING) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Read the length of the string (next 4 bytes) + uint32_t string_size = BSON::deserialize_int32(data, index); + // Adjust index to point to start of string + index = index + 4; + // Decode the string and add zero terminating value at the end of the string + char *value = (char *)malloc((string_size * sizeof(char))); + strncpy(value, (data + index), string_size); + // Encode the string (string - null termiating character) + Local utf8_encoded_str = Encode(value, string_size - 1, UTF8)->ToString(); + // Add the value to the data + if(is_array_item) { + return_array->Set(Number::New(insert_index), utf8_encoded_str); + } else { + return_data->ForceSet(String::New(string_name), utf8_encoded_str); + } + + // Adjust index + index = index + string_size; + // Free up the memory + free(value); + free(string_name); + } else if(type == BSON_DATA_INT) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Decode the integer value + uint32_t value = 0; + memcpy(&value, (data + index), 4); + + // Adjust the index for the size of the value + index = index + 4; + // Add the element to the object + if(is_array_item) { + return_array->Set(Integer::New(insert_index), Integer::New(value)); + } else { + return_data->ForceSet(String::New(string_name), Integer::New(value)); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_TIMESTAMP) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), BSON::decodeTimestamp(bson, data, index)); + } else { + return_data->ForceSet(String::New(string_name), BSON::decodeTimestamp(bson, data, index)); + } + + // Adjust the index for the size of the value + index = index + 8; + + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_LONG) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), BSON::decodeLong(bson, data, index)); + } else { + return_data->ForceSet(String::New(string_name), BSON::decodeLong(bson, data, index)); + } + + // Adjust the index for the size of the value + index = index + 8; + + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_NUMBER) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Decode the integer value + double value = 0; + memcpy(&value, (data + index), 8); + // Adjust the index for the size of the value + index = index + 8; + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), Number::New(value)); + } else { + return_data->ForceSet(String::New(string_name), Number::New(value)); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_MIN_KEY) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Create new MinKey + Local minKey = bson->minKeyConstructor->NewInstance(); + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), minKey); + } else { + return_data->ForceSet(String::New(string_name), minKey); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_MAX_KEY) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Create new MinKey + Local maxKey = bson->maxKeyConstructor->NewInstance(); + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), maxKey); + } else { + return_data->ForceSet(String::New(string_name), maxKey); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_NULL) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), Null()); + } else { + return_data->ForceSet(String::New(string_name), Null()); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_BOOLEAN) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Decode the boolean value + char bool_value = *(data + index); + // Adjust the index for the size of the value + index = index + 1; + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), bool_value == 1 ? Boolean::New(true) : Boolean::New(false)); + } else { + return_data->ForceSet(String::New(string_name), bool_value == 1 ? Boolean::New(true) : Boolean::New(false)); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_DATE) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Decode the value 64 bit integer + int64_t value = 0; + memcpy(&value, (data + index), 8); + // Adjust the index for the size of the value + index = index + 8; + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), Date::New((double)value)); + } else { + return_data->ForceSet(String::New(string_name), Date::New((double)value)); + } + // Free up the memory + free(string_name); + } else if(type == BSON_DATA_REGEXP) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Length variable + int32_t length_regexp = 0; + char chr; + + // Locate end of the regexp expression \0 + while((chr = *(data + index + length_regexp)) != '\0') { + length_regexp = length_regexp + 1; + } + + // Contains the reg exp + char *reg_exp = (char *)malloc(length_regexp * sizeof(char) + 2); + // Copy the regexp from the data to the char * + memcpy(reg_exp, (data + index), (length_regexp + 1)); + // Adjust the index to skip the first part of the regular expression + index = index + length_regexp + 1; + + // Reset the length + int32_t options_length = 0; + // Locate the end of the options for the regexp terminated with a '\0' + while((chr = *(data + index + options_length)) != '\0') { + options_length = options_length + 1; + } + + // Contains the reg exp + char *options = (char *)malloc(options_length * sizeof(char) + 1); + // Copy the options from the data to the char * + memcpy(options, (data + index), (options_length + 1)); + // Adjust the index to skip the option part of the regular expression + index = index + options_length + 1; + // ARRRRGH Google does not expose regular expressions through the v8 api + // Have to use Script to instantiate the object (slower) + + // Generate the string for execution in the string context + int flag = 0; + + for(int i = 0; i < options_length; i++) { + // Multiline + if(*(options + i) == 'm') { + flag = flag | 4; + } else if(*(options + i) == 'i') { + flag = flag | 2; + } + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), RegExp::New(String::New(reg_exp), (v8::RegExp::Flags)flag)); + } else { + return_data->ForceSet(String::New(string_name), RegExp::New(String::New(reg_exp), (v8::RegExp::Flags)flag)); + } + + // Free memory + free(reg_exp); + free(options); + free(string_name); + } else if(type == BSON_DATA_OID) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // The id string + char *oid_string = (char *)malloc(12 * sizeof(char)); + // Copy the options from the data to the char * + memcpy(oid_string, (data + index), 12); + + // Adjust the index + index = index + 12; + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), BSON::decodeOid(bson, oid_string)); + } else { + return_data->ForceSet(String::New(string_name), BSON::decodeOid(bson, oid_string)); + } + + // Free memory + free(oid_string); + free(string_name); + } else if(type == BSON_DATA_BINARY) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Read the binary data size + uint32_t number_of_bytes = BSON::deserialize_int32(data, index); + // Adjust the index + index = index + 4; + // Decode the subtype, ensure it's positive + uint32_t sub_type = (int)*(data + index) & 0xff; + // Adjust the index + index = index + 1; + // Copy the binary data into a buffer + char *buffer = (char *)malloc(number_of_bytes * sizeof(char) + 1); + memcpy(buffer, (data + index), number_of_bytes); + *(buffer + number_of_bytes) = '\0'; + + // Adjust the index + index = index + number_of_bytes; + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), BSON::decodeBinary(bson, sub_type, number_of_bytes, buffer)); + } else { + return_data->ForceSet(String::New(string_name), BSON::decodeBinary(bson, sub_type, number_of_bytes, buffer)); + } + // Free memory + free(buffer); + free(string_name); + } else if(type == BSON_DATA_SYMBOL) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Read the length of the string (next 4 bytes) + uint32_t string_size = BSON::deserialize_int32(data, index); + // Adjust index to point to start of string + index = index + 4; + // Decode the string and add zero terminating value at the end of the string + char *value = (char *)malloc((string_size * sizeof(char))); + strncpy(value, (data + index), string_size); + // Encode the string (string - null termiating character) + Local utf8_encoded_str = Encode(value, string_size - 1, UTF8)->ToString(); + + // Wrap up the string in a Symbol Object + Local argv[] = {utf8_encoded_str}; + Handle symbolObj = bson->symbolConstructor->NewInstance(1, argv); + + // Add the value to the data + if(is_array_item) { + return_array->Set(Number::New(insert_index), symbolObj); + } else { + return_data->ForceSet(String::New(string_name), symbolObj); + } + + // Adjust index + index = index + string_size; + // Free up the memory + free(value); + free(string_name); + } else if(type == BSON_DATA_CODE) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Read the string size + uint32_t string_size = BSON::deserialize_int32(data, index); + // Adjust the index + index = index + 4; + // Read the string + char *code = (char *)malloc(string_size * sizeof(char) + 1); + // Copy string + terminating 0 + memcpy(code, (data + index), string_size); + + // Define empty scope object + Handle scope_object = Object::New(); + + // Define the try catch block + TryCatch try_catch; + // Decode the code object + Handle obj = BSON::decodeCode(bson, code, scope_object); + // If an error was thrown push it up the chain + if(try_catch.HasCaught()) { + free(string_name); + free(code); + // Rethrow exception + return try_catch.ReThrow(); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), obj); + } else { + return_data->ForceSet(String::New(string_name), obj); + } + + // Clean up memory allocation + free(code); + free(string_name); + } else if(type == BSON_DATA_CODE_W_SCOPE) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Total number of bytes after array index + uint32_t total_code_size = BSON::deserialize_int32(data, index); + // Adjust the index + index = index + 4; + // Read the string size + uint32_t string_size = BSON::deserialize_int32(data, index); + // Adjust the index + index = index + 4; + // Read the string + char *code = (char *)malloc(string_size * sizeof(char) + 1); + // Copy string + terminating 0 + memcpy(code, (data + index), string_size); + // Adjust the index + index = index + string_size; + // Get the scope object (bson object) + uint32_t bson_object_size = total_code_size - string_size - 8; + // Allocate bson object buffer and copy out the content + char *bson_buffer = (char *)malloc(bson_object_size * sizeof(char)); + memcpy(bson_buffer, (data + index), bson_object_size); + // Adjust the index + index = index + bson_object_size; + // Parse the bson object + Handle scope_object = BSON::deserialize(bson, bson_buffer, inDataLength, 0, false); + // Define the try catch block + TryCatch try_catch; + // Decode the code object + Handle obj = BSON::decodeCode(bson, code, scope_object); + // If an error was thrown push it up the chain + if(try_catch.HasCaught()) { + // Clean up memory allocation + free(string_name); + free(bson_buffer); + free(code); + // Rethrow exception + return try_catch.ReThrow(); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), obj); + } else { + return_data->ForceSet(String::New(string_name), obj); + } + + // Clean up memory allocation + free(code); + free(bson_buffer); + free(string_name); + } else if(type == BSON_DATA_OBJECT) { + // If this is the top level object we need to skip the undecoding + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Get the object size + uint32_t bson_object_size = BSON::deserialize_int32(data, index); + // Define the try catch block + TryCatch try_catch; + // Decode the code object + Handle obj = BSON::deserialize(bson, data + index, inDataLength, 0, false); + // Adjust the index + index = index + bson_object_size; + // If an error was thrown push it up the chain + if(try_catch.HasCaught()) { + // Rethrow exception + return try_catch.ReThrow(); + } + + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), obj); + } else { + return_data->ForceSet(String::New(string_name), obj); + } + + // Clean up memory allocation + free(string_name); + } else if(type == BSON_DATA_ARRAY) { + // Read the null terminated index String + char *string_name = BSON::extract_string(data, index); + if(string_name == NULL) return VException("Invalid C String found."); + // Let's create a new string + index = index + strlen(string_name) + 1; + // Handle array value if applicable + uint32_t insert_index = 0; + if(is_array_item) { + insert_index = atoi(string_name); + } + + // Get the size + uint32_t array_size = BSON::deserialize_int32(data, index); + // Define the try catch block + TryCatch try_catch; + + // Decode the code object + Handle obj = BSON::deserialize(bson, data + index, inDataLength, 0, true); + // If an error was thrown push it up the chain + if(try_catch.HasCaught()) { + // Rethrow exception + return try_catch.ReThrow(); + } + // Adjust the index for the next value + index = index + array_size; + // Add the element to the object + if(is_array_item) { + return_array->Set(Number::New(insert_index), obj); + } else { + return_data->ForceSet(String::New(string_name), obj); + } + // Clean up memory allocation + free(string_name); + } + } + + // Check if we have a db reference + if(!is_array_item && return_data->Has(String::New("$ref")) && return_data->Has(String::New("$id"))) { + Handle dbrefValue = BSON::decodeDBref(bson, return_data->Get(String::New("$ref")), return_data->Get(String::New("$id")), return_data->Get(String::New("$db"))); + return scope.Close(dbrefValue); + } + + // Return the data object to javascript + if(is_array_item) { + return scope.Close(return_array); + } else { + return scope.Close(return_data); + } +} + +Handle BSON::BSONSerialize(const Arguments &args) { + HandleScope scope; + + if(args.Length() == 1 && !args[0]->IsObject()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 3 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]"); + if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean() && !args[3]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]"); + if(args.Length() > 4) return VException("One, two, tree or four arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]"); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + uint32_t object_size = 0; + // Calculate the total size of the document in binary form to ensure we only allocate memory once + // With serialize function + if(args.Length() == 4) { + object_size = BSON::calculate_object_size(bson, args[0], args[3]->BooleanValue()); + } else { + object_size = BSON::calculate_object_size(bson, args[0], false); + } + + // Allocate the memory needed for the serializtion + char *serialized_object = (char *)malloc(object_size * sizeof(char)); + // Catch any errors + try { + // Check if we have a boolean value + bool check_key = false; + if(args.Length() >= 3 && args[1]->IsBoolean()) { + check_key = args[1]->BooleanValue(); + } + + // Check if we have a boolean value + bool serializeFunctions = false; + if(args.Length() == 4 && args[1]->IsBoolean()) { + serializeFunctions = args[3]->BooleanValue(); + } + + // Serialize the object + BSON::serialize(bson, serialized_object, 0, Null(), args[0], check_key, serializeFunctions); + } catch(char *err_msg) { + // Free up serialized object space + free(serialized_object); + V8::AdjustAmountOfExternalAllocatedMemory(-object_size); + // Throw exception with the string + Handle error = VException(err_msg); + // free error message + free(err_msg); + // Return error + return error; + } + + // Write the object size + BSON::write_int32((serialized_object), object_size); + + // If we have 3 arguments + if(args.Length() == 3 || args.Length() == 4) { + // Local asBuffer = args[2]->ToBoolean(); + Buffer *buffer = Buffer::New(serialized_object, object_size); + // Release the serialized string + free(serialized_object); + return scope.Close(buffer->handle_); + } else { + // Encode the string (string - null termiating character) + Local bin_value = Encode(serialized_object, object_size, BINARY)->ToString(); + // Return the serialized content + return bin_value; + } +} + +Handle BSON::CalculateObjectSize(const Arguments &args) { + HandleScope scope; + // Ensure we have a valid object + if(args.Length() == 1 && !args[0]->IsObject()) return VException("One argument required - [object]"); + if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("Two arguments required - [object, boolean]"); + if(args.Length() > 3) return VException("One or two arguments required - [object] or [object, boolean]"); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // Object size + uint32_t object_size = 0; + // Check if we have our argument, calculate size of the object + if(args.Length() >= 2) { + object_size = BSON::calculate_object_size(bson, args[0], args[1]->BooleanValue()); + } else { + object_size = BSON::calculate_object_size(bson, args[0], false); + } + + // Return the object size + return scope.Close(Uint32::New(object_size)); +} + +uint32_t BSON::calculate_object_size(BSON *bson, Handle value, bool serializeFunctions) { + uint32_t object_size = 0; + + // If we have an object let's unwrap it and calculate the sub sections + if(value->IsString()) { + // Let's calculate the size the string adds, length + type(1 byte) + size(4 bytes) + object_size += value->ToString()->Utf8Length() + 1 + 4; + } else if(value->IsNumber()) { + // Check if we have a float value or a long value + Local number = value->ToNumber(); + double d_number = number->NumberValue(); + int64_t l_number = number->IntegerValue(); + // Check if we have a double value and not a int64 + double d_result = d_number - l_number; + // If we have a value after subtracting the integer value we have a float + if(d_result > 0 || d_result < 0) { + object_size = object_size + 8; + } else if(l_number <= BSON_INT32_MAX && l_number >= BSON_INT32_MIN) { + object_size = object_size + 4; + } else { + object_size = object_size + 8; + } + } else if(value->IsBoolean()) { + object_size = object_size + 1; + } else if(value->IsDate()) { + object_size = object_size + 8; + } else if(value->IsRegExp()) { + // Fetch the string for the regexp + Handle regExp = Handle::Cast(value); + ssize_t len = DecodeBytes(regExp->GetSource(), UTF8); + int flags = regExp->GetFlags(); + + // global + if((flags & (1 << 0)) != 0) len++; + // ignorecase + if((flags & (1 << 1)) != 0) len++; + //multiline + if((flags & (1 << 2)) != 0) len++; + // if((flags & (1 << 2)) != 0) len++; + // Calculate the space needed for the regexp: size of string - 2 for the /'ses +2 for null termiations + object_size = object_size + len + 2; + } else if(value->IsNull() || value->IsUndefined()) { + } else if(value->IsArray()) { + // Cast to array + Local array = Local::Cast(value->ToObject()); + // Turn length into string to calculate the size of all the strings needed + char *length_str = (char *)malloc(256 * sizeof(char)); + // Calculate the size of each element + for(uint32_t i = 0; i < array->Length(); i++) { + // Add "index" string size for each element + sprintf(length_str, "%d", i); + // Add the size of the string length + uint32_t label_length = strlen(length_str) + 1; + // Add the type definition size for each item + object_size = object_size + label_length + 1; + // Add size of the object + uint32_t object_length = BSON::calculate_object_size(bson, array->Get(Integer::New(i)), serializeFunctions); + object_size = object_size + object_length; + } + // Add the object size + object_size = object_size + 4 + 1; + // Free up memory + free(length_str); + } else if(value->IsFunction()) { + if(serializeFunctions) { + object_size += value->ToString()->Utf8Length() + 4 + 1; + } + } else if(value->ToObject()->Has(bson->_bsontypeString)) { + // Handle holder + Local constructorString = value->ToObject()->GetConstructorName(); + + // BSON type object, avoid non-needed checking unless we have a type + if(bson->longString->StrictEquals(constructorString)) { + object_size = object_size + 8; + } else if(bson->timestampString->StrictEquals(constructorString)) { + object_size = object_size + 8; + } else if(bson->objectIDString->StrictEquals(constructorString)) { + object_size = object_size + 12; + } else if(bson->binaryString->StrictEquals(constructorString)) { + // Unpack the object and encode + Local positionObj = value->ToObject()->Get(String::New("position"))->ToUint32(); + // Adjust the object_size, binary content lengt + total size int32 + binary size int32 + subtype + object_size += positionObj->Value() + 4 + 1; + } else if(bson->codeString->StrictEquals(constructorString)) { + // Unpack the object and encode + Local obj = value->ToObject(); + // Get the function + Local function = obj->Get(String::New("code"))->ToString(); + // Get the scope object + Local scope = obj->Get(String::New("scope"))->ToObject(); + + // For Node < 0.6.X use the GetPropertyNames + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + uint32_t propertyNameLength = scope->GetPropertyNames()->Length(); + #else + uint32_t propertyNameLength = scope->GetOwnPropertyNames()->Length(); + #endif + + // Check if the scope has any parameters + // Let's calculate the size the code object adds adds + if(propertyNameLength > 0) { + object_size += function->Utf8Length() + 4 + BSON::calculate_object_size(bson, scope, serializeFunctions) + 4 + 1; + } else { + object_size += function->Utf8Length() + 4 + 1; + } + } else if(bson->dbrefString->StrictEquals(constructorString)) { + // Unpack the dbref + Local dbref = value->ToObject(); + // Create an object containing the right namespace variables + Local obj = Object::New(); + // Build the new object + obj->Set(bson->_dbRefRefString, dbref->Get(bson->_dbRefNamespaceString)); + obj->Set(bson->_dbRefIdRefString, dbref->Get(bson->_dbRefOidString)); + if(!dbref->Get(bson->_dbRefDbString)->IsNull() && !dbref->Get(bson->_dbRefDbString)->IsUndefined()) obj->Set(bson->_dbRefDbRefString, dbref->Get(bson->_dbRefDbString)); + // Calculate size + object_size += BSON::calculate_object_size(bson, obj, serializeFunctions); + } else if(bson->minKeyString->StrictEquals(constructorString) || bson->maxKeyString->Equals(constructorString)) { + } else if(bson->symbolString->StrictEquals(constructorString)) { + // Get string + Local str = value->ToObject()->Get(String::New("value"))->ToString(); + // Get the utf8 length + int utf8_length = str->Utf8Length(); + // Check if we have a utf8 encoded string or not + if(utf8_length != str->Length()) { + // Let's calculate the size the string adds, length + type(1 byte) + size(4 bytes) + object_size += str->Utf8Length() + 1 + 4; + } else { + object_size += str->Length() + 1 + 4; + } + } else if(bson->doubleString->StrictEquals(constructorString)) { + object_size = object_size + 8; + } + } else if(value->IsObject()) { + // Unwrap the object + Local object = value->ToObject(); + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + Local property_names = object->GetPropertyNames(); + #else + Local property_names = object->GetOwnPropertyNames(); + #endif + + // Length of the property + uint32_t propertyLength = property_names->Length(); + + // Process all the properties on the object + for(uint32_t index = 0; index < propertyLength; index++) { + // Fetch the property name + Local property_name = property_names->Get(index)->ToString(); + + // Fetch the object for the property + Local property = object->Get(property_name); + // Get size of property (property + property name length + 1 for terminating 0) + if(!property->IsFunction() || (property->IsFunction() && serializeFunctions)) { + // Convert name to char* + object_size += BSON::calculate_object_size(bson, property, serializeFunctions) + property_name->Utf8Length() + 1 + 1; + } + } + + object_size = object_size + 4 + 1; + } + + return object_size; +} + +uint32_t BSON::serialize(BSON *bson, char *serialized_object, uint32_t index, Handle name, Handle value, bool check_key, bool serializeFunctions) { + // Scope for method execution + HandleScope scope; + + // If we have a name check that key is valid + if(!name->IsNull() && check_key) { + if(BSON::check_key(name->ToString()) != NULL) return -1; + } + + // If we have an object let's serialize it + if(value->IsString()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_STRING; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Write the actual string into the char array + Local str = value->ToString(); + // Let's fetch the int value + uint32_t utf8_length = str->Utf8Length(); + + // Write the integer to the char * + BSON::write_int32((serialized_object + index), utf8_length + 1); + // Adjust the index + index = index + 4; + // Write string to char in utf8 format + str->WriteUtf8((serialized_object + index), utf8_length); + // Add the null termination + *(serialized_object + index + utf8_length) = '\0'; + // Adjust the index + index = index + utf8_length + 1; + } else if(value->IsNumber()) { + uint32_t first_pointer = index; + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_INT; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + Local number = value->ToNumber(); + // Get the values + double d_number = number->NumberValue(); + int64_t l_number = number->IntegerValue(); + + // Check if we have a double value and not a int64 + double d_result = d_number - l_number; + // If we have a value after subtracting the integer value we have a float + if(d_result > 0 || d_result < 0) { + // Write the double to the char array + BSON::write_double((serialized_object + index), d_number); + // Adjust type to be double + *(serialized_object + first_pointer) = BSON_DATA_NUMBER; + // Adjust index for double + index = index + 8; + } else if(l_number <= BSON_INT32_MAX && l_number >= BSON_INT32_MIN) { + // Smaller than 32 bit, write as 32 bit value + BSON::write_int32(serialized_object + index, value->ToInt32()->Value()); + // Adjust the size of the index + index = index + 4; + } else if(l_number <= JS_INT_MAX && l_number >= JS_INT_MIN) { + // Write the double to the char array + BSON::write_double((serialized_object + index), d_number); + // Adjust type to be double + *(serialized_object + first_pointer) = BSON_DATA_NUMBER; + // Adjust index for double + index = index + 8; + } else { + BSON::write_double((serialized_object + index), d_number); + // Adjust type to be double + *(serialized_object + first_pointer) = BSON_DATA_NUMBER; + // Adjust the size of the index + index = index + 8; + } + } else if(value->IsBoolean()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_BOOLEAN; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Save the boolean value + *(serialized_object + index) = value->BooleanValue() ? '\1' : '\0'; + // Adjust the index + index = index + 1; + } else if(value->IsDate()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_DATE; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Fetch the Integer value + int64_t integer_value = value->IntegerValue(); + BSON::write_int64((serialized_object + index), integer_value); + // Adjust the index + index = index + 8; + } else if(value->IsNull() || value->IsUndefined()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_NULL; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + } else if(value->IsArray()) { + // Cast to array + Local array = Local::Cast(value->ToObject()); + // Turn length into string to calculate the size of all the strings needed + char *length_str = (char *)malloc(256 * sizeof(char)); + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_ARRAY; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + // Object size + uint32_t object_size = BSON::calculate_object_size(bson, value, serializeFunctions); + // Write the size of the object + BSON::write_int32((serialized_object + index), object_size); + // Adjust the index + index = index + 4; + // Write out all the elements + for(uint32_t i = 0; i < array->Length(); i++) { + // Add "index" string size for each element + sprintf(length_str, "%d", i); + // Encode the values + index = BSON::serialize(bson, serialized_object, index, String::New(length_str), array->Get(Integer::New(i)), check_key, serializeFunctions); + // Write trailing '\0' for object + *(serialized_object + index) = '\0'; + } + + // Pad the last item + *(serialized_object + index) = '\0'; + index = index + 1; + // Free up memory + free(length_str); + } else if(value->IsRegExp()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_REGEXP; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Fetch the string for the regexp + Handle regExp = Handle::Cast(value); + len = DecodeBytes(regExp->GetSource(), UTF8); + written = DecodeWrite((serialized_object + index), len, regExp->GetSource(), UTF8); + int flags = regExp->GetFlags(); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // global + if((flags & (1 << 0)) != 0) { + *(serialized_object + index) = 's'; + index = index + 1; + } + + // ignorecase + if((flags & (1 << 1)) != 0) { + *(serialized_object + index) = 'i'; + index = index + 1; + } + + //multiline + if((flags & (1 << 2)) != 0) { + *(serialized_object + index) = 'm'; + index = index + 1; + } + + // Add null termiation for the string + *(serialized_object + index) = '\0'; + // Adjust the index + index = index + 1; + } else if(value->IsFunction()) { + if(serializeFunctions) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_CODE; + + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + + // Function String + Local function = value->ToString(); + + // Decode the function + len = DecodeBytes(function, BINARY); + // Write the size of the code string + 0 byte end of cString + BSON::write_int32((serialized_object + index), len + 1); + // Adjust the index + index = index + 4; + + // Write the data into the serialization stream + written = DecodeWrite((serialized_object + index), len, function, BINARY); + // Write \0 for string + *(serialized_object + index + len) = 0x00; + // Adjust the index + index = index + len + 1; + } + } else if(value->ToObject()->Has(bson->_bsontypeString)) { + // Handle holder + Local constructorString = value->ToObject()->GetConstructorName(); + uint32_t originalIndex = index; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + // Add null termiation for the string + *(serialized_object + index + len) = 0x00; + // Adjust the index + index = index + len + 1; + + // BSON type object, avoid non-needed checking unless we have a type + if(bson->longString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_LONG; + // Object reference + Local longObject = value->ToObject(); + + // Fetch the low and high bits + int32_t lowBits = longObject->Get(bson->_longLowString)->ToInt32()->Value(); + int32_t highBits = longObject->Get(bson->_longHighString)->ToInt32()->Value(); + + // Write the content to the char array + BSON::write_int32((serialized_object + index), lowBits); + BSON::write_int32((serialized_object + index + 4), highBits); + // Adjust the index + index = index + 8; + } else if(bson->timestampString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_TIMESTAMP; + // Object reference + Local timestampObject = value->ToObject(); + + // Fetch the low and high bits + int32_t lowBits = timestampObject->Get(bson->_longLowString)->ToInt32()->Value(); + int32_t highBits = timestampObject->Get(bson->_longHighString)->ToInt32()->Value(); + + // Write the content to the char array + BSON::write_int32((serialized_object + index), lowBits); + BSON::write_int32((serialized_object + index + 4), highBits); + // Adjust the index + index = index + 8; + } else if(bson->objectIDString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_OID; + // Convert to object + Local objectIDObject = value->ToObject(); + // Let's grab the id + Local idString = objectIDObject->Get(bson->_objectIDidString)->ToString(); + // Let's decode the raw chars from the string + len = DecodeBytes(idString, BINARY); + written = DecodeWrite((serialized_object + index), len, idString, BINARY); + // Adjust the index + index = index + 12; + } else if(bson->binaryString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_BINARY; + + // Let's get the binary object + Local binaryObject = value->ToObject(); + + // Grab the size(position of the binary) + uint32_t position = value->ToObject()->Get(bson->_binaryPositionString)->ToUint32()->Value(); + // Grab the subtype + uint32_t subType = value->ToObject()->Get(bson->_binarySubTypeString)->ToUint32()->Value(); + // Grab the buffer object + Local bufferObj = value->ToObject()->Get(bson->_binaryBufferString)->ToObject(); + + // Buffer data pointers + char *data; + uint32_t length; + + // Unpack the buffer variable + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(bufferObj); + data = buffer->data(); + length = buffer->length(); + #else + data = Buffer::Data(bufferObj); + length = Buffer::Length(bufferObj); + #endif + + // Write the size of the buffer out + BSON::write_int32((serialized_object + index), position); + // Adjust index + index = index + 4; + // Write subtype + *(serialized_object + index) = (char)subType; + // Adjust index + index = index + 1; + // Write binary content + memcpy((serialized_object + index), data, position); + // Adjust index.rar">_ + index = index + position; + } else if(bson->doubleString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_NUMBER; + + // Unpack the double + Local doubleObject = value->ToObject(); + + // Fetch the double value + Local doubleValue = doubleObject->Get(bson->_doubleValueString)->ToNumber(); + // Write the double to the char array + BSON::write_double((serialized_object + index), doubleValue->NumberValue()); + // Adjust index for double + index = index + 8; + } else if(bson->symbolString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_SYMBOL; + // Unpack symbol object + Local symbolObj = value->ToObject(); + + // Grab the actual string + Local str = symbolObj->Get(bson->_symbolValueString)->ToString(); + // Let's fetch the int value + int utf8_length = str->Utf8Length(); + + // If the Utf8 length is different from the string length then we + // have a UTF8 encoded string, otherwise write it as ascii + if(utf8_length != str->Length()) { + // Write the integer to the char * + BSON::write_int32((serialized_object + index), utf8_length + 1); + // Adjust the index + index = index + 4; + // Write string to char in utf8 format + str->WriteUtf8((serialized_object + index), utf8_length); + // Add the null termination + *(serialized_object + index + utf8_length) = '\0'; + // Adjust the index + index = index + utf8_length + 1; + } else { + // Write the integer to the char * + BSON::write_int32((serialized_object + index), str->Length() + 1); + // Adjust the index + index = index + 4; + // Write string to char in utf8 format + written = DecodeWrite((serialized_object + index), str->Length(), str, BINARY); + // Add the null termination + *(serialized_object + index + str->Length()) = '\0'; + // Adjust the index + index = index + str->Length() + 1; + } + } else if(bson->codeString->StrictEquals(constructorString)) { + // Unpack the object and encode + Local obj = value->ToObject(); + // Get the function + Local function = obj->Get(String::New("code"))->ToString(); + // Get the scope object + Local scope = obj->Get(String::New("scope"))->ToObject(); + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + uint32_t propertyNameLength = scope->GetPropertyNames()->Length(); + #else + uint32_t propertyNameLength = scope->GetOwnPropertyNames()->Length(); + #endif + + // Set the right type if we have a scope or not + if(propertyNameLength > 0) { + // Set basic data code object with scope object + *(serialized_object + originalIndex) = BSON_DATA_CODE_W_SCOPE; + + // Calculate the size of the whole object + uint32_t scopeSize = BSON::calculate_object_size(bson, scope, false); + // Decode the function length + ssize_t len = DecodeBytes(function, UTF8); + // Calculate total size + uint32_t size = 4 + len + 1 + 4 + scopeSize; + + // Write the total size + BSON::write_int32((serialized_object + index), size); + // Adjust the index + index = index + 4; + + // Write the function size + BSON::write_int32((serialized_object + index), len + 1); + // Adjust the index + index = index + 4; + + // Write the data into the serialization stream + ssize_t written = DecodeWrite((serialized_object + index), len, function, UTF8); + assert(written == len); + // Write \0 for string + *(serialized_object + index + len) = 0x00; + // Adjust the index with the length of the function + index = index + len + 1; + // Write the scope object + BSON::serialize(bson, (serialized_object + index), 0, Null(), scope, check_key, serializeFunctions); + // Adjust the index + index = index + scopeSize; + } else { + // Set basic data code object + *(serialized_object + originalIndex) = BSON_DATA_CODE; + // Decode the function + ssize_t len = DecodeBytes(function, BINARY); + // Write the size of the code string + 0 byte end of cString + BSON::write_int32((serialized_object + index), len + 1); + // Adjust the index + index = index + 4; + + // Write the data into the serialization stream + ssize_t written = DecodeWrite((serialized_object + index), len, function, BINARY); + assert(written == len); + // Write \0 for string + *(serialized_object + index + len) = 0x00; + // Adjust the index + index = index + len + 1; + } + } else if(bson->dbrefString->StrictEquals(constructorString)) { + // Unpack the dbref + Local dbref = value->ToObject(); + // Create an object containing the right namespace variables + Local obj = Object::New(); + + // Build the new object + obj->Set(bson->_dbRefRefString, dbref->Get(bson->_dbRefNamespaceString)); + obj->Set(bson->_dbRefIdRefString, dbref->Get(bson->_dbRefOidString)); + if(!dbref->Get(bson->_dbRefDbString)->IsNull() && !dbref->Get(bson->_dbRefDbString)->IsUndefined()) obj->Set(bson->_dbRefDbRefString, dbref->Get(bson->_dbRefDbString)); + + // Encode the variable + index = BSON::serialize(bson, serialized_object, originalIndex, name, obj, false, serializeFunctions); + } else if(bson->minKeyString->StrictEquals(constructorString)) { + // Save the string at the offset provided + *(serialized_object + originalIndex) = BSON_DATA_MIN_KEY; + } else if(bson->maxKeyString->StrictEquals(constructorString)) { + *(serialized_object + originalIndex) = BSON_DATA_MAX_KEY; + } + } else if(value->IsObject()) { + if(!name->IsNull()) { + // Save the string at the offset provided + *(serialized_object + index) = BSON_DATA_OBJECT; + // Adjust writing position for the first byte + index = index + 1; + // Convert name to char* + ssize_t len = DecodeBytes(name, UTF8); + ssize_t written = DecodeWrite((serialized_object + index), len, name, UTF8); + assert(written == len); + // Add null termiation for the string + *(serialized_object + index + len) = '\0'; + // Adjust the index + index = index + len + 1; + } + + // Unwrap the object + Local object = value->ToObject(); + + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6 + Local property_names = object->GetPropertyNames(); + #else + Local property_names = object->GetOwnPropertyNames(); + #endif + + // Calculate size of the total object + uint32_t object_size = BSON::calculate_object_size(bson, value, serializeFunctions); + // Write the size + BSON::write_int32((serialized_object + index), object_size); + // Adjust size + index = index + 4; + + // Process all the properties on the object + for(uint32_t i = 0; i < property_names->Length(); i++) { + // Fetch the property name + Local property_name = property_names->Get(i)->ToString(); + // Fetch the object for the property + Local property = object->Get(property_name); + // Write the next serialized object + // printf("========== !property->IsFunction() || (property->IsFunction() && serializeFunctions) = %d\n", !property->IsFunction() || (property->IsFunction() && serializeFunctions) == true ? 1 : 0); + if(!property->IsFunction() || (property->IsFunction() && serializeFunctions)) { + // Convert name to char* + ssize_t len = DecodeBytes(property_name, UTF8); + // char *data = new char[len]; + char *data = (char *)malloc(len + 1); + *(data + len) = '\0'; + ssize_t written = DecodeWrite(data, len, property_name, UTF8); + assert(written == len); + // Serialize the content + index = BSON::serialize(bson, serialized_object, index, property_name, property, check_key, serializeFunctions); + // Free up memory of data + free(data); + } + } + // Pad the last item + *(serialized_object + index) = '\0'; + index = index + 1; + + // Null out reminding fields if we have a toplevel object and nested levels + if(name->IsNull()) { + for(uint32_t i = 0; i < (object_size - index); i++) { + *(serialized_object + index + i) = '\0'; + } + } + } + + return index; +} + +Handle BSON::SerializeWithBufferAndIndex(const Arguments &args) { + HandleScope scope; + + //BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, checkKeys, buffer, index) { + // Ensure we have the correct values + if(args.Length() > 5) return VException("Four or five parameters required [object, boolean, Buffer, int] or [object, boolean, Buffer, int, boolean]"); + if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32()) return VException("Four parameters required [object, boolean, Buffer, int]"); + if(args.Length() == 5 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32() && !args[4]->IsBoolean()) return VException("Four parameters required [object, boolean, Buffer, int, boolean]"); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // Define pointer to data + char *data; + uint32_t length; + // Unpack the object + Local obj = args[2]->ToObject(); + + // Unpack the buffer object and get pointers to structures + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(obj); + data = buffer->data(); + length = buffer->length(); + #else + data = Buffer::Data(obj); + length = Buffer::Length(obj); + #endif + + uint32_t object_size = 0; + // Calculate the total size of the document in binary form to ensure we only allocate memory once + if(args.Length() == 5) { + object_size = BSON::calculate_object_size(bson, args[0], args[4]->BooleanValue()); + } else { + object_size = BSON::calculate_object_size(bson, args[0], false); + } + + // Unpack the index variable + Local indexObject = args[3]->ToUint32(); + uint32_t index = indexObject->Value(); + + // Allocate the memory needed for the serializtion + char *serialized_object = (char *)malloc(object_size * sizeof(char)); + + // Catch any errors + try { + // Check if we have a boolean value + bool check_key = false; + if(args.Length() >= 4 && args[1]->IsBoolean()) { + check_key = args[1]->BooleanValue(); + } + + bool serializeFunctions = false; + if(args.Length() == 5) { + serializeFunctions = args[4]->BooleanValue(); + } + + // Serialize the object + BSON::serialize(bson, serialized_object, 0, Null(), args[0], check_key, serializeFunctions); + } catch(char *err_msg) { + // Free up serialized object space + free(serialized_object); + V8::AdjustAmountOfExternalAllocatedMemory(-object_size); + // Throw exception with the string + Handle error = VException(err_msg); + // free error message + free(err_msg); + // Return error + return error; + } + + for(uint32_t i = 0; i < object_size; i++) { + *(data + index + i) = *(serialized_object + i); + } + + return scope.Close(Uint32::New(index + object_size - 1)); +} + +Handle BSON::BSONDeserializeStream(const Arguments &args) { + HandleScope scope; + + // At least 3 arguments required + if(args.Length() < 5) VException("Arguments required (Buffer(data), Number(index in data), Number(number of documents to deserialize), Array(results), Number(index in the array), Object(optional))"); + + // If the number of argumets equals 3 + if(args.Length() >= 5) { + if(!Buffer::HasInstance(args[0])) return VException("First argument must be Buffer instance"); + if(!args[1]->IsUint32()) return VException("Second argument must be a positive index number"); + if(!args[2]->IsUint32()) return VException("Third argument must be a positive number of documents to deserialize"); + if(!args[3]->IsArray()) return VException("Fourth argument must be an array the size of documents to deserialize"); + if(!args[4]->IsUint32()) return VException("Sixth argument must be a positive index number"); + } + + // If we have 4 arguments + if(args.Length() == 6 && !args[5]->IsObject()) return VException("Fifth argument must be an object with options"); + + // Define pointer to data + char *data; + uint32_t length; + Local obj = args[0]->ToObject(); + uint32_t numberOfDocuments = args[2]->ToUint32()->Value(); + uint32_t index = args[1]->ToUint32()->Value(); + uint32_t resultIndex = args[4]->ToUint32()->Value(); + + // Unpack the BSON parser instance + BSON *bson = ObjectWrap::Unwrap(args.This()); + + // Unpack the buffer variable + #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3 + Buffer *buffer = ObjectWrap::Unwrap(obj); + data = buffer->data(); + length = buffer->length(); + #else + data = Buffer::Data(obj); + length = Buffer::Length(obj); + #endif + + // Fetch the documents + Local documents = args[3]->ToObject(); + + for(uint32_t i = 0; i < numberOfDocuments; i++) { + // Decode the size of the BSON data structure + uint32_t size = BSON::deserialize_int32(data, index); + + // Get result + Handle result = BSON::deserialize(bson, data, size, index, NULL); + + // Add result to array + documents->Set(i + resultIndex, result); + + // Adjust the index for next pass + index = index + size; + } + + // Return new index of parsing + return scope.Close(Uint32::New(index)); +} + +// Exporting function +extern "C" void init(Handle target) { + HandleScope scope; + BSON::Initialize(target); +} + +// NODE_MODULE(bson, BSON::Initialize); +// NODE_MODULE(l, Long::Initialize); diff --git a/node_modules/mongodb/node_modules/bson/ext/bson.h b/node_modules/mongodb/node_modules/bson/ext/bson.h new file mode 100644 index 0000000..dcf21d1 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/ext/bson.h @@ -0,0 +1,105 @@ +#ifndef BSON_H_ +#define BSON_H_ + +#include +#include +#include + +using namespace v8; +using namespace node; + +class BSON : public ObjectWrap { + public: + BSON() : ObjectWrap() {} + ~BSON() {} + + static void Initialize(Handle target); + static Handle BSONDeserializeStream(const Arguments &args); + + // JS based objects + static Handle BSONSerialize(const Arguments &args); + static Handle BSONDeserialize(const Arguments &args); + + // Calculate size of function + static Handle CalculateObjectSize(const Arguments &args); + static Handle SerializeWithBufferAndIndex(const Arguments &args); + + // Experimental + static Handle CalculateObjectSize2(const Arguments &args); + static Handle BSONSerialize2(const Arguments &args); + + // Constructor used for creating new BSON objects from C++ + static Persistent constructor_template; + + private: + static Handle New(const Arguments &args); + static Handle deserialize(BSON *bson, char *data, uint32_t dataLength, uint32_t startIndex, bool is_array_item); + static uint32_t serialize(BSON *bson, char *serialized_object, uint32_t index, Handle name, Handle value, bool check_key, bool serializeFunctions); + + static char* extract_string(char *data, uint32_t offset); + static const char* ToCString(const v8::String::Utf8Value& value); + static uint32_t calculate_object_size(BSON *bson, Handle object, bool serializeFunctions); + + static void write_int32(char *data, uint32_t value); + static void write_int64(char *data, int64_t value); + static void write_double(char *data, double value); + static uint16_t deserialize_int8(char *data, uint32_t offset); + static uint32_t deserialize_int32(char* data, uint32_t offset); + static char *check_key(Local key); + + // BSON type instantiate functions + Persistent longConstructor; + Persistent objectIDConstructor; + Persistent binaryConstructor; + Persistent codeConstructor; + Persistent dbrefConstructor; + Persistent symbolConstructor; + Persistent doubleConstructor; + Persistent timestampConstructor; + Persistent minKeyConstructor; + Persistent maxKeyConstructor; + + // Equality Objects + Persistent longString; + Persistent objectIDString; + Persistent binaryString; + Persistent codeString; + Persistent dbrefString; + Persistent symbolString; + Persistent doubleString; + Persistent timestampString; + Persistent minKeyString; + Persistent maxKeyString; + + // Equality speed up comparision objects + Persistent _bsontypeString; + Persistent _longLowString; + Persistent _longHighString; + Persistent _objectIDidString; + Persistent _binaryPositionString; + Persistent _binarySubTypeString; + Persistent _binaryBufferString; + Persistent _doubleValueString; + Persistent _symbolValueString; + + Persistent _dbRefRefString; + Persistent _dbRefIdRefString; + Persistent _dbRefDbRefString; + Persistent _dbRefNamespaceString; + Persistent _dbRefDbString; + Persistent _dbRefOidString; + + // Decode JS function + static Handle decodeLong(BSON *bson, char *data, uint32_t index); + static Handle decodeTimestamp(BSON *bson, char *data, uint32_t index); + static Handle decodeOid(BSON *bson, char *oid); + static Handle decodeBinary(BSON *bson, uint32_t sub_type, uint32_t number_of_bytes, char *data); + static Handle decodeCode(BSON *bson, char *code, Handle scope); + static Handle decodeDBref(BSON *bson, Local ref, Local oid, Local db); + + // Experimental + static uint32_t calculate_object_size2(Handle object); + static uint32_t serialize2(char *serialized_object, uint32_t index, Handle name, Handle value, uint32_t object_size, bool check_key); +}; + +#endif // BSON_H_ diff --git a/node_modules/mongodb/node_modules/bson/ext/index.js b/node_modules/mongodb/node_modules/bson/ext/index.js new file mode 100644 index 0000000..9c45d53 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/ext/index.js @@ -0,0 +1,20 @@ +var bson = require('./bson'); +exports.BSON = bson.BSON; +exports.Long = require('../lib/bson/long').Long; +exports.ObjectID = require('../lib/bson/objectid').ObjectID; +exports.DBRef = require('../lib/bson/db_ref').DBRef; +exports.Code = require('../lib/bson/code').Code; +exports.Timestamp = require('../lib/bson/timestamp').Timestamp; +exports.Binary = require('../lib/bson/binary').Binary; +exports.Double = require('../lib/bson/double').Double; +exports.MaxKey = require('../lib/bson/max_key').MaxKey; +exports.MinKey = require('../lib/bson/min_key').MinKey; +exports.Symbol = require('../lib/bson/symbol').Symbol; + +// Just add constants tot he Native BSON parser +exports.BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0; +exports.BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1; +exports.BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +exports.BSON.BSON_BINARY_SUBTYPE_UUID = 3; +exports.BSON.BSON_BINARY_SUBTYPE_MD5 = 4; +exports.BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; diff --git a/node_modules/mongodb/node_modules/bson/ext/wscript b/node_modules/mongodb/node_modules/bson/ext/wscript new file mode 100644 index 0000000..40f5317 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/ext/wscript @@ -0,0 +1,39 @@ +import Options +from os import unlink, symlink, popen +from os.path import exists + +srcdir = "." +blddir = "build" +VERSION = "0.1.0" + +def set_options(opt): + opt.tool_options("compiler_cxx") + opt.add_option( '--debug' + , action='store_true' + , default=False + , help='Build debug variant [Default: False]' + , dest='debug' + ) + +def configure(conf): + conf.check_tool("compiler_cxx") + conf.check_tool("node_addon") + conf.env.append_value('CXXFLAGS', ['-O3', '-funroll-loops']) + + # conf.env.append_value('CXXFLAGS', ['-DDEBUG', '-g', '-O0', '-Wall', '-Wextra']) + # conf.check(lib='node', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='NODE') + +def build(bld): + obj = bld.new_task_gen("cxx", "shlib", "node_addon") + obj.target = "bson" + obj.source = ["bson.cc"] + # obj.uselib = "NODE" + +def shutdown(): + # HACK to get compress.node out of build directory. + # better way to do this? + if Options.commands['clean']: + if exists('bson.node'): unlink('bson.node') + else: + if exists('build/default/bson.node') and not exists('bson.node'): + symlink('build/default/bson.node', 'bson.node') diff --git a/node_modules/mongodb/node_modules/bson/install.js b/node_modules/mongodb/node_modules/bson/install.js new file mode 100644 index 0000000..c9cc91d --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/install.js @@ -0,0 +1,41 @@ +var spawn = require('child_process').spawn, + exec = require('child_process').exec; + +process.stdout.write("================================================================================\n"); +process.stdout.write("= =\n"); +process.stdout.write("= To install with C++ bson parser do =\n"); +process.stdout.write("= =\n"); +process.stdout.write("================================================================================\n"); + +// Check if we want to build the native code +var build_native = process.env['npm_config_mongodb_native'] != null ? process.env['npm_config_mongodb_native'] : 'false'; +build_native = build_native == 'true' ? true : false; + +// If we are building the native bson extension ensure we use gmake if available +if(build_native) { + // Check if we need to use gmake + exec('which gmake', function(err, stdout, stderr) { + // Set up spawn command + var make = null; + // No gmake build using make + if(err != null) { + make = spawn('make', ['total'], {cwd:process.env['PWD']}); + } else { + make = spawn('gmake', ['total'], {cwd:process.env['PWD']}); + } + + // Execute spawn + make.stdout.on('data', function(data) { + process.stdout.write(data); + }) + + make.stderr.on('data', function(data) { + process.stdout.write(data); + }) + + make.on('exit', function(code) { + process.stdout.write('child process exited with code ' + code + "\n"); + }) + }); +} + diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/binary.js b/node_modules/mongodb/node_modules/bson/lib/bson/binary.js new file mode 100644 index 0000000..eaa0dad --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/binary.js @@ -0,0 +1,336 @@ +/** + * Module dependencies. + */ +if(typeof window === 'undefined') { + var Buffer = require('buffer').Buffer; // TODO just use global Buffer + var bson = require('./bson'); +} + +// Binary default subtype +var BSON_BINARY_SUBTYPE_DEFAULT = 0; + +/** + * @ignore + * @api private + */ +var writeStringToArray = function(data) { + // Create a buffer + var buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(data.length)) : new Array(data.length); + // Write the content to the buffer + for(var i = 0; i < data.length; i++) { + buffer[i] = data.charCodeAt(i); + } + // Write the string to the buffer + return buffer; +} + +/** + * Convert Array ot Uint8Array to Binary String + * + * @ignore + * @api private + */ +var convertArraytoUtf8BinaryString = function(byteArray, startIndex, endIndex) { + var result = ""; + for(var i = startIndex; i < endIndex; i++) { + result = result + String.fromCharCode(byteArray[i]); + } + return result; +}; + +/** + * A class representation of the BSON Binary type. + * + * Sub types + * - **BSON.BSON_BINARY_SUBTYPE_DEFAULT**, default BSON type. + * - **BSON.BSON_BINARY_SUBTYPE_FUNCTION**, BSON function type. + * - **BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY**, BSON byte array type. + * - **BSON.BSON_BINARY_SUBTYPE_UUID**, BSON uuid type. + * - **BSON.BSON_BINARY_SUBTYPE_MD5**, BSON md5 type. + * - **BSON.BSON_BINARY_SUBTYPE_USER_DEFINED**, BSON user defined type. + * + * @class Represents the Binary BSON type. + * @param {Buffer} buffer a buffer object containing the binary data. + * @param {Number} [subType] the option binary type. + * @return {Grid} + */ +function Binary(buffer, subType) { + if(!(this instanceof Binary)) return new Binary(buffer, subType); + + this._bsontype = 'Binary'; + + if(buffer instanceof Number) { + this.sub_type = buffer; + this.position = 0; + } else { + this.sub_type = subType == null ? BSON_BINARY_SUBTYPE_DEFAULT : subType; + this.position = 0; + } + + if(buffer != null && !(buffer instanceof Number)) { + // Only accept Buffer, Uint8Array or Arrays + if(typeof buffer == 'string') { + // Different ways of writing the length of the string for the different types + if(typeof Buffer != 'undefined') { + this.buffer = new Buffer(buffer); + } else if(typeof Uint8Array != 'undefined' || (Object.prototype.toString.call(buffer) == '[object Array]')) { + this.buffer = writeStringToArray(buffer); + } else { + throw new Error("only String, Buffer, Uint8Array or Array accepted"); + } + } else { + this.buffer = buffer; + } + this.position = buffer.length; + } else { + if(typeof Buffer != 'undefined') { + this.buffer = new Buffer(Binary.BUFFER_SIZE); + } else if(typeof Uint8Array != 'undefined'){ + this.buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE)); + } else { + this.buffer = new Array(Binary.BUFFER_SIZE); + } + // Set position to start of buffer + this.position = 0; + } +}; + +/** + * Updates this binary with byte_value. + * + * @param {Character} byte_value a single byte we wish to write. + * @api public + */ +Binary.prototype.put = function put(byte_value) { + // If it's a string and a has more than one character throw an error + if(byte_value['length'] != null && typeof byte_value != 'number' && byte_value.length != 1) throw new Error("only accepts single character String, Uint8Array or Array"); + if(typeof byte_value != 'number' && byte_value < 0 || byte_value > 255) throw new Error("only accepts number in a valid unsigned byte range 0-255"); + + // Decode the byte value once + var decoded_byte = null; + if(typeof byte_value == 'string') { + decoded_byte = byte_value.charCodeAt(0); + } else if(byte_value['length'] != null) { + decoded_byte = byte_value[0]; + } else { + decoded_byte = byte_value; + } + + if(this.buffer.length > this.position) { + this.buffer[this.position++] = decoded_byte; + } else { + if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { + // Create additional overflow buffer + var buffer = new Buffer(Binary.BUFFER_SIZE + this.buffer.length); + // Combine the two buffers together + this.buffer.copy(buffer, 0, 0, this.buffer.length); + this.buffer = buffer; + this.buffer[this.position++] = decoded_byte; + } else { + var buffer = null; + // Create a new buffer (typed or normal array) + if(Object.prototype.toString.call(this.buffer) == '[object Uint8Array]') { + buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE + this.buffer.length)); + } else { + buffer = new Array(Binary.BUFFER_SIZE + this.buffer.length); + } + + // We need to copy all the content to the new array + for(var i = 0; i < this.buffer.length; i++) { + buffer[i] = this.buffer[i]; + } + + // Reassign the buffer + this.buffer = buffer; + // Write the byte + this.buffer[this.position++] = decoded_byte; + } + } +}; + +/** + * Writes a buffer or string to the binary. + * + * @param {Buffer|String} string a string or buffer to be written to the Binary BSON object. + * @param {Number} offset specify the binary of where to write the content. + * @api public + */ +Binary.prototype.write = function write(string, offset) { + offset = typeof offset == 'number' ? offset : this.position; + + // If the buffer is to small let's extend the buffer + if(this.buffer.length < offset + string.length) { + var buffer = null; + // If we are in node.js + if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { + buffer = new Buffer(this.buffer.length + string.length); + this.buffer.copy(buffer, 0, 0, this.buffer.length); + } else if(Object.prototype.toString.call(this.buffer) == '[object Uint8Array]') { + // Create a new buffer + buffer = new Uint8Array(new ArrayBuffer(this.buffer.length + string.length)) + // Copy the content + for(var i = 0; i < this.position; i++) { + buffer[i] = this.buffer[i]; + } + } + + // Assign the new buffer + this.buffer = buffer; + } + + if(typeof Buffer != 'undefined' && Buffer.isBuffer(string) && Buffer.isBuffer(this.buffer)) { + string.copy(this.buffer, offset, 0, string.length); + this.position = (offset + string.length) > this.position ? (offset + string.length) : this.position; + // offset = string.length + } else if(typeof Buffer != 'undefined' && typeof string == 'string' && Buffer.isBuffer(this.buffer)) { + this.buffer.write(string, 'binary', offset); + this.position = (offset + string.length) > this.position ? (offset + string.length) : this.position; + // offset = string.length; + } else if(Object.prototype.toString.call(string) == '[object Uint8Array]' + || Object.prototype.toString.call(string) == '[object Array]' && typeof string != 'string') { + for(var i = 0; i < string.length; i++) { + this.buffer[offset++] = string[i]; + } + + this.position = offset > this.position ? offset : this.position; + } else if(typeof string == 'string') { + for(var i = 0; i < string.length; i++) { + this.buffer[offset++] = string.charCodeAt(i); + } + + this.position = offset > this.position ? offset : this.position; + } +}; + +/** + * Reads **length** bytes starting at **position**. + * + * @param {Number} position read from the given position in the Binary. + * @param {Number} length the number of bytes to read. + * @return {Buffer} + * @api public + */ +Binary.prototype.read = function read(position, length) { + length = length && length > 0 + ? length + : this.position; + + // Let's return the data based on the type we have + if(this.buffer['slice']) { + return this.buffer.slice(position, position + length); + } else { + // Create a buffer to keep the result + var buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(length)) : new Array(length); + for(var i = 0; i < length; i++) { + buffer[i] = this.buffer[position++]; + } + } + // Return the buffer + return buffer; +}; + +/** + * Returns the value of this binary as a string. + * + * @return {String} + * @api public + */ +Binary.prototype.value = function value(asRaw) { + asRaw = asRaw == null ? false : asRaw; + + // If it's a node.js buffer object + if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { + return asRaw ? this.buffer.slice(0, this.position) : this.buffer.toString('binary', 0, this.position); + } else { + if(asRaw) { + // we support the slice command use it + if(this.buffer['slice'] != null) { + return this.buffer.slice(0, this.position); + } else { + // Create a new buffer to copy content to + var newBuffer = Object.prototype.toString.call(this.buffer) == '[object Uint8Array]' ? new Uint8Array(new ArrayBuffer(this.position)) : new Array(this.position); + // Copy content + for(var i = 0; i < this.position; i++) { + newBuffer[i] = this.buffer[i]; + } + // Return the buffer + return newBuffer; + } + } else { + return convertArraytoUtf8BinaryString(this.buffer, 0, this.position); + } + } +}; + +/** + * Length. + * + * @return {Number} the length of the binary. + * @api public + */ +Binary.prototype.length = function length() { + return this.position; +}; + +/** + * @ignore + * @api private + */ +Binary.prototype.toJSON = function() { + return this.buffer != null ? this.buffer.toString('base64') : ''; +} + +/** + * @ignore + * @api private + */ +Binary.prototype.toString = function(format) { + return this.buffer != null ? this.buffer.slice(0, this.position).toString(format) : ''; +} + +Binary.BUFFER_SIZE = 256; + +/** + * Default BSON type + * + * @classconstant SUBTYPE_DEFAULT + **/ +Binary.SUBTYPE_DEFAULT = 0; +/** + * Function BSON type + * + * @classconstant SUBTYPE_DEFAULT + **/ +Binary.SUBTYPE_FUNCTION = 1; +/** + * Byte Array BSON type + * + * @classconstant SUBTYPE_DEFAULT + **/ +Binary.SUBTYPE_BYTE_ARRAY = 2; +/** + * UUID BSON type + * + * @classconstant SUBTYPE_DEFAULT + **/ +Binary.SUBTYPE_UUID = 3; +/** + * MD5 BSON type + * + * @classconstant SUBTYPE_DEFAULT + **/ +Binary.SUBTYPE_MD5 = 4; +/** + * User BSON type + * + * @classconstant SUBTYPE_DEFAULT + **/ +Binary.SUBTYPE_USER_DEFINED = 128; + +/** + * Expose. + */ +if(typeof window === 'undefined') { + exports.Binary = Binary; +} + diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/binary_parser.js b/node_modules/mongodb/node_modules/bson/lib/bson/binary_parser.js new file mode 100644 index 0000000..29951e9 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/binary_parser.js @@ -0,0 +1,387 @@ +/** + * Binary Parser. + * Jonas Raoni Soares Silva + * http://jsfromhell.com/classes/binary-parser [v1.0] + */ +var chr = String.fromCharCode; + +var maxBits = []; +for (var i = 0; i < 64; i++) { + maxBits[i] = Math.pow(2, i); +} + +function BinaryParser (bigEndian, allowExceptions) { + if(!(this instanceof BinaryParser)) return new BinaryParser(bigEndian, allowExceptions); + + this.bigEndian = bigEndian; + this.allowExceptions = allowExceptions; +}; + +BinaryParser.warn = function warn (msg) { + if (this.allowExceptions) { + throw new Error(msg); + } + + return 1; +}; + +BinaryParser.decodeFloat = function decodeFloat (data, precisionBits, exponentBits) { + var b = new this.Buffer(this.bigEndian, data); + + b.checkBuffer(precisionBits + exponentBits + 1); + + var bias = maxBits[exponentBits - 1] - 1 + , signal = b.readBits(precisionBits + exponentBits, 1) + , exponent = b.readBits(precisionBits, exponentBits) + , significand = 0 + , divisor = 2 + , curByte = b.buffer.length + (-precisionBits >> 3) - 1; + + do { + for (var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 ); + } while (precisionBits -= startBit); + + return exponent == ( bias << 1 ) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity : ( 1 + signal * -2 ) * ( exponent || significand ? !exponent ? Math.pow( 2, -bias + 1 ) * significand : Math.pow( 2, exponent - bias ) * ( 1 + significand ) : 0 ); +}; + +BinaryParser.decodeInt = function decodeInt (data, bits, signed, forceBigEndian) { + var b = new this.Buffer(this.bigEndian || forceBigEndian, data) + , x = b.readBits(0, bits) + , max = maxBits[bits]; //max = Math.pow( 2, bits ); + + return signed && x >= max / 2 + ? x - max + : x; +}; + +BinaryParser.encodeFloat = function encodeFloat (data, precisionBits, exponentBits) { + var bias = maxBits[exponentBits - 1] - 1 + , minExp = -bias + 1 + , maxExp = bias + , minUnnormExp = minExp - precisionBits + , n = parseFloat(data) + , status = isNaN(n) || n == -Infinity || n == +Infinity ? n : 0 + , exp = 0 + , len = 2 * bias + 1 + precisionBits + 3 + , bin = new Array(len) + , signal = (n = status !== 0 ? 0 : n) < 0 + , intPart = Math.floor(n = Math.abs(n)) + , floatPart = n - intPart + , lastBit + , rounded + , result + , i + , j; + + for (i = len; i; bin[--i] = 0); + + for (i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor(intPart / 2)); + + for (i = bias + 1; floatPart > 0 && i; (bin[++i] = ((floatPart *= 2) >= 1) - 0 ) && --floatPart); + + for (i = -1; ++i < len && !bin[i];); + + if (bin[(lastBit = precisionBits - 1 + (i = (exp = bias + 1 - i) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - (exp = minExp - 1))) + 1]) { + if (!(rounded = bin[lastBit])) { + for (j = lastBit + 2; !rounded && j < len; rounded = bin[j++]); + } + + for (j = lastBit + 1; rounded && --j >= 0; (bin[j] = !bin[j] - 0) && (rounded = 0)); + } + + for (i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i];); + + if ((exp = bias + 1 - i) >= minExp && exp <= maxExp) { + ++i; + } else if (exp < minExp) { + exp != bias + 1 - len && exp < minUnnormExp && this.warn("encodeFloat::float underflow"); + i = bias + 1 - (exp = minExp - 1); + } + + if (intPart || status !== 0) { + this.warn(intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status); + exp = maxExp + 1; + i = bias + 2; + + if (status == -Infinity) { + signal = 1; + } else if (isNaN(status)) { + bin[i] = 1; + } + } + + for (n = Math.abs(exp + bias), j = exponentBits + 1, result = ""; --j; result = (n % 2) + result, n = n >>= 1); + + for (n = 0, j = 0, i = (result = (signal ? "1" : "0") + result + bin.slice(i, i + precisionBits).join("")).length, r = []; i; j = (j + 1) % 8) { + n += (1 << j) * result.charAt(--i); + if (j == 7) { + r[r.length] = String.fromCharCode(n); + n = 0; + } + } + + r[r.length] = n + ? String.fromCharCode(n) + : ""; + + return (this.bigEndian ? r.reverse() : r).join(""); +}; + +BinaryParser.encodeInt = function encodeInt (data, bits, signed, forceBigEndian) { + var max = maxBits[bits]; + + if (data >= max || data < -(max / 2)) { + this.warn("encodeInt::overflow"); + data = 0; + } + + if (data < 0) { + data += max; + } + + for (var r = []; data; r[r.length] = String.fromCharCode(data % 256), data = Math.floor(data / 256)); + + for (bits = -(-bits >> 3) - r.length; bits--; r[r.length] = "\0"); + + return ((this.bigEndian || forceBigEndian) ? r.reverse() : r).join(""); +}; + +BinaryParser.toSmall = function( data ){ return this.decodeInt( data, 8, true ); }; +BinaryParser.fromSmall = function( data ){ return this.encodeInt( data, 8, true ); }; +BinaryParser.toByte = function( data ){ return this.decodeInt( data, 8, false ); }; +BinaryParser.fromByte = function( data ){ return this.encodeInt( data, 8, false ); }; +BinaryParser.toShort = function( data ){ return this.decodeInt( data, 16, true ); }; +BinaryParser.fromShort = function( data ){ return this.encodeInt( data, 16, true ); }; +BinaryParser.toWord = function( data ){ return this.decodeInt( data, 16, false ); }; +BinaryParser.fromWord = function( data ){ return this.encodeInt( data, 16, false ); }; +BinaryParser.toInt = function( data ){ return this.decodeInt( data, 32, true ); }; +BinaryParser.fromInt = function( data ){ return this.encodeInt( data, 32, true ); }; +BinaryParser.toLong = function( data ){ return this.decodeInt( data, 64, true ); }; +BinaryParser.fromLong = function( data ){ return this.encodeInt( data, 64, true ); }; +BinaryParser.toDWord = function( data ){ return this.decodeInt( data, 32, false ); }; +BinaryParser.fromDWord = function( data ){ return this.encodeInt( data, 32, false ); }; +BinaryParser.toQWord = function( data ){ return this.decodeInt( data, 64, true ); }; +BinaryParser.fromQWord = function( data ){ return this.encodeInt( data, 64, true ); }; +BinaryParser.toFloat = function( data ){ return this.decodeFloat( data, 23, 8 ); }; +BinaryParser.fromFloat = function( data ){ return this.encodeFloat( data, 23, 8 ); }; +BinaryParser.toDouble = function( data ){ return this.decodeFloat( data, 52, 11 ); }; +BinaryParser.fromDouble = function( data ){ return this.encodeFloat( data, 52, 11 ); }; + +// Factor out the encode so it can be shared by add_header and push_int32 +BinaryParser.encode_int32 = function encode_int32 (number, asArray) { + var a, b, c, d, unsigned; + unsigned = (number < 0) ? (number + 0x100000000) : number; + a = Math.floor(unsigned / 0xffffff); + unsigned &= 0xffffff; + b = Math.floor(unsigned / 0xffff); + unsigned &= 0xffff; + c = Math.floor(unsigned / 0xff); + unsigned &= 0xff; + d = Math.floor(unsigned); + return asArray ? [chr(a), chr(b), chr(c), chr(d)] : chr(a) + chr(b) + chr(c) + chr(d); +}; + +BinaryParser.encode_int64 = function encode_int64 (number) { + var a, b, c, d, e, f, g, h, unsigned; + unsigned = (number < 0) ? (number + 0x10000000000000000) : number; + a = Math.floor(unsigned / 0xffffffffffffff); + unsigned &= 0xffffffffffffff; + b = Math.floor(unsigned / 0xffffffffffff); + unsigned &= 0xffffffffffff; + c = Math.floor(unsigned / 0xffffffffff); + unsigned &= 0xffffffffff; + d = Math.floor(unsigned / 0xffffffff); + unsigned &= 0xffffffff; + e = Math.floor(unsigned / 0xffffff); + unsigned &= 0xffffff; + f = Math.floor(unsigned / 0xffff); + unsigned &= 0xffff; + g = Math.floor(unsigned / 0xff); + unsigned &= 0xff; + h = Math.floor(unsigned); + return chr(a) + chr(b) + chr(c) + chr(d) + chr(e) + chr(f) + chr(g) + chr(h); +}; + +/** + * UTF8 methods + */ + +// Take a raw binary string and return a utf8 string +BinaryParser.decode_utf8 = function decode_utf8 (binaryStr) { + var len = binaryStr.length + , decoded = '' + , i = 0 + , c = 0 + , c1 = 0 + , c2 = 0 + , c3; + + while (i < len) { + c = binaryStr.charCodeAt(i); + if (c < 128) { + decoded += String.fromCharCode(c); + i++; + } else if ((c > 191) && (c < 224)) { + c2 = binaryStr.charCodeAt(i+1); + decoded += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = binaryStr.charCodeAt(i+1); + c3 = binaryStr.charCodeAt(i+2); + decoded += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + + return decoded; +}; + +// Encode a cstring +BinaryParser.encode_cstring = function encode_cstring (s) { + return unescape(encodeURIComponent(s)) + BinaryParser.fromByte(0); +}; + +// Take a utf8 string and return a binary string +BinaryParser.encode_utf8 = function encode_utf8 (s) { + var a = "" + , c; + + for (var n = 0, len = s.length; n < len; n++) { + c = s.charCodeAt(n); + + if (c < 128) { + a += String.fromCharCode(c); + } else if ((c > 127) && (c < 2048)) { + a += String.fromCharCode((c>>6) | 192) ; + a += String.fromCharCode((c&63) | 128); + } else { + a += String.fromCharCode((c>>12) | 224); + a += String.fromCharCode(((c>>6) & 63) | 128); + a += String.fromCharCode((c&63) | 128); + } + } + + return a; +}; + +BinaryParser.hprint = function hprint (s) { + var number; + + for (var i = 0, len = s.length; i < len; i++) { + if (s.charCodeAt(i) < 32) { + number = s.charCodeAt(i) <= 15 + ? "0" + s.charCodeAt(i).toString(16) + : s.charCodeAt(i).toString(16); + process.stdout.write(number + " ") + } else { + number = s.charCodeAt(i) <= 15 + ? "0" + s.charCodeAt(i).toString(16) + : s.charCodeAt(i).toString(16); + process.stdout.write(number + " ") + } + } + + process.stdout.write("\n\n"); +}; + +BinaryParser.ilprint = function hprint (s) { + var number; + + for (var i = 0, len = s.length; i < len; i++) { + if (s.charCodeAt(i) < 32) { + number = s.charCodeAt(i) <= 15 + ? "0" + s.charCodeAt(i).toString(10) + : s.charCodeAt(i).toString(10); + + require('util').debug(number+' : '); + } else { + number = s.charCodeAt(i) <= 15 + ? "0" + s.charCodeAt(i).toString(10) + : s.charCodeAt(i).toString(10); + require('util').debug(number+' : '+ s.charAt(i)); + } + } +}; + +BinaryParser.hlprint = function hprint (s) { + var number; + + for (var i = 0, len = s.length; i < len; i++) { + if (s.charCodeAt(i) < 32) { + number = s.charCodeAt(i) <= 15 + ? "0" + s.charCodeAt(i).toString(16) + : s.charCodeAt(i).toString(16); + require('util').debug(number+' : '); + } else { + number = s.charCodeAt(i) <= 15 + ? "0" + s.charCodeAt(i).toString(16) + : s.charCodeAt(i).toString(16); + require('util').debug(number+' : '+ s.charAt(i)); + } + } +}; + +/** + * BinaryParser buffer constructor. + */ +function BinaryParserBuffer (bigEndian, buffer) { + this.bigEndian = bigEndian || 0; + this.buffer = []; + this.setBuffer(buffer); +}; + +BinaryParserBuffer.prototype.setBuffer = function setBuffer (data) { + var l, i, b; + + if (data) { + i = l = data.length; + b = this.buffer = new Array(l); + for (; i; b[l - i] = data.charCodeAt(--i)); + this.bigEndian && b.reverse(); + } +}; + +BinaryParserBuffer.prototype.hasNeededBits = function hasNeededBits (neededBits) { + return this.buffer.length >= -(-neededBits >> 3); +}; + +BinaryParserBuffer.prototype.checkBuffer = function checkBuffer (neededBits) { + if (!this.hasNeededBits(neededBits)) { + throw new Error("checkBuffer::missing bytes"); + } +}; + +BinaryParserBuffer.prototype.readBits = function readBits (start, length) { + //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni) + + function shl (a, b) { + for (; b--; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1); + return a; + } + + if (start < 0 || length <= 0) { + return 0; + } + + this.checkBuffer(start + length); + + var offsetLeft + , offsetRight = start % 8 + , curByte = this.buffer.length - ( start >> 3 ) - 1 + , lastByte = this.buffer.length + ( -( start + length ) >> 3 ) + , diff = curByte - lastByte + , sum = ((this.buffer[ curByte ] >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1)) + (diff && (offsetLeft = (start + length) % 8) ? (this.buffer[lastByte++] & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight : 0); + + for(; diff; sum += shl(this.buffer[lastByte++], (diff-- << 3) - offsetRight)); + + return sum; +}; + +/** + * Expose. + */ +BinaryParser.Buffer = BinaryParserBuffer; + +if(typeof window === 'undefined') { + exports.BinaryParser = BinaryParser; +} diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/bson.js b/node_modules/mongodb/node_modules/bson/lib/bson/bson.js new file mode 100644 index 0000000..d24de4b --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/bson.js @@ -0,0 +1,1483 @@ +if(typeof window === 'undefined') { + var Long = require('./long').Long + , Double = require('./double').Double + , Timestamp = require('./timestamp').Timestamp + , ObjectID = require('./objectid').ObjectID + , Symbol = require('./symbol').Symbol + , Code = require('./code').Code + , MinKey = require('./min_key').MinKey + , MaxKey = require('./max_key').MaxKey + , DBRef = require('./db_ref').DBRef + , Binary = require('./binary').Binary + , BinaryParser = require('./binary_parser').BinaryParser + , writeIEEE754 = require('./float_parser').writeIEEE754 + , readIEEE754 = require('./float_parser').readIEEE754; +} + +/** + * Create a new BSON instance + * + * @class Represents the BSON Parser + * @return {BSON} instance of BSON Parser. + */ +function BSON () {}; + +/** + * @ignore + * @api private + */ +// BSON MAX VALUES +BSON.BSON_INT32_MAX = 0x7FFFFFFF; +BSON.BSON_INT32_MIN = -0x80000000; + +BSON.BSON_INT64_MAX = Math.pow(2, 63) - 1; +BSON.BSON_INT64_MIN = -Math.pow(2, 63); + +// JS MAX PRECISE VALUES +BSON.JS_INT_MAX = 0x20000000000000; // Any integer up to 2^53 can be precisely represented by a double. +BSON.JS_INT_MIN = -0x20000000000000; // Any integer down to -2^53 can be precisely represented by a double. + +// Internal long versions +var JS_INT_MAX_LONG = Long.fromNumber(0x20000000000000); // Any integer up to 2^53 can be precisely represented by a double. +var JS_INT_MIN_LONG = Long.fromNumber(-0x20000000000000); // Any integer down to -2^53 can be precisely represented by a double. + +/** + * Number BSON Type + * + * @classconstant BSON_DATA_NUMBER + **/ +BSON.BSON_DATA_NUMBER = 1; +/** + * String BSON Type + * + * @classconstant BSON_DATA_STRING + **/ +BSON.BSON_DATA_STRING = 2; +/** + * Object BSON Type + * + * @classconstant BSON_DATA_OBJECT + **/ +BSON.BSON_DATA_OBJECT = 3; +/** + * Array BSON Type + * + * @classconstant BSON_DATA_ARRAY + **/ +BSON.BSON_DATA_ARRAY = 4; +/** + * Binary BSON Type + * + * @classconstant BSON_DATA_BINARY + **/ +BSON.BSON_DATA_BINARY = 5; +/** + * ObjectID BSON Type + * + * @classconstant BSON_DATA_OID + **/ +BSON.BSON_DATA_OID = 7; +/** + * Boolean BSON Type + * + * @classconstant BSON_DATA_BOOLEAN + **/ +BSON.BSON_DATA_BOOLEAN = 8; +/** + * Date BSON Type + * + * @classconstant BSON_DATA_DATE + **/ +BSON.BSON_DATA_DATE = 9; +/** + * null BSON Type + * + * @classconstant BSON_DATA_NULL + **/ +BSON.BSON_DATA_NULL = 10; +/** + * RegExp BSON Type + * + * @classconstant BSON_DATA_REGEXP + **/ +BSON.BSON_DATA_REGEXP = 11; +/** + * Code BSON Type + * + * @classconstant BSON_DATA_CODE + **/ +BSON.BSON_DATA_CODE = 13; +/** + * Symbol BSON Type + * + * @classconstant BSON_DATA_SYMBOL + **/ +BSON.BSON_DATA_SYMBOL = 14; +/** + * Code with Scope BSON Type + * + * @classconstant BSON_DATA_CODE_W_SCOPE + **/ +BSON.BSON_DATA_CODE_W_SCOPE = 15; +/** + * 32 bit Integer BSON Type + * + * @classconstant BSON_DATA_INT + **/ +BSON.BSON_DATA_INT = 16; +/** + * Timestamp BSON Type + * + * @classconstant BSON_DATA_TIMESTAMP + **/ +BSON.BSON_DATA_TIMESTAMP = 17; +/** + * Long BSON Type + * + * @classconstant BSON_DATA_LONG + **/ +BSON.BSON_DATA_LONG = 18; +/** + * MinKey BSON Type + * + * @classconstant BSON_DATA_MIN_KEY + **/ +BSON.BSON_DATA_MIN_KEY = 0xff; +/** + * MaxKey BSON Type + * + * @classconstant BSON_DATA_MAX_KEY + **/ +BSON.BSON_DATA_MAX_KEY = 0x7f; + +/** + * Binary Default Type + * + * @classconstant BSON_BINARY_SUBTYPE_DEFAULT + **/ +BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0; +/** + * Binary Function Type + * + * @classconstant BSON_BINARY_SUBTYPE_FUNCTION + **/ +BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1; +/** + * Binary Byte Array Type + * + * @classconstant BSON_BINARY_SUBTYPE_BYTE_ARRAY + **/ +BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +/** + * Binary UUID Type + * + * @classconstant BSON_BINARY_SUBTYPE_UUID + **/ +BSON.BSON_BINARY_SUBTYPE_UUID = 3; +/** + * Binary MD5 Type + * + * @classconstant BSON_BINARY_SUBTYPE_MD5 + **/ +BSON.BSON_BINARY_SUBTYPE_MD5 = 4; +/** + * Binary User Defined Type + * + * @classconstant BSON_BINARY_SUBTYPE_USER_DEFINED + **/ +BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; + +/** + * Calculate the bson size for a passed in Javascript object. + * + * @param {Object} object the Javascript object to calculate the BSON byte size for. + * @param {Boolean} [serializeFunctions] serialize all functions in the object **(default:false)**. + * @return {Number} returns the number of bytes the BSON object will take up. + * @api public + */ +BSON.calculateObjectSize = function calculateObjectSize(object, serializeFunctions) { + var totalLength = (4 + 1); + + if(Array.isArray(object)) { + for(var i = 0; i < object.length; i++) { + totalLength += calculateElement(i.toString(), object[i], serializeFunctions) + } + } else { + for(var key in object) { + totalLength += calculateElement(key, object[key], serializeFunctions) + } + } + + return totalLength; +} + +/** + * @ignore + * @api private + */ +function calculateElement(name, value, serializeFunctions) { + var isBuffer = typeof Buffer !== 'undefined'; + + switch(typeof value) { + case 'string': + return 1 + (!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1 + 4 + (!isBuffer ? numberOfBytes(value) : Buffer.byteLength(value, 'utf8')) + 1; + case 'number': + if(Math.floor(value) === value && value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { + if(value >= BSON.BSON_INT32_MIN && value <= BSON.BSON_INT32_MAX) { // 32 bit + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (4 + 1); + } else { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); + } + } else { // 64 bit + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); + } + case 'undefined': + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1); + case 'boolean': + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1 + 1); + case 'object': + if(value == null || value instanceof MinKey || value instanceof MaxKey || value['_bsontype'] == 'MinKey' || value['_bsontype'] == 'MaxKey') { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1); + } else if(value instanceof ObjectID || value['_bsontype'] == 'ObjectID') { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (12 + 1); + } else if(value instanceof Date) { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); + } else if(typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1 + 4 + 1) + value.length; + } else if(value instanceof Long || value instanceof Double || value instanceof Timestamp + || value['_bsontype'] == 'Long' || value['_bsontype'] == 'Double' || value['_bsontype'] == 'Timestamp') { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); + } else if(value instanceof Code || value['_bsontype'] == 'Code') { + // Calculate size depending on the availability of a scope + if(value.scope != null && Object.keys(value.scope).length > 0) { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + 4 + (!isBuffer ? numberOfBytes(value.code.toString()) : Buffer.byteLength(value.code.toString(), 'utf8')) + 1 + BSON.calculateObjectSize(value.scope, serializeFunctions); + } else { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + (!isBuffer ? numberOfBytes(value.code.toString()) : Buffer.byteLength(value.code.toString(), 'utf8')) + 1; + } + } else if(value instanceof Binary || value['_bsontype'] == 'Binary') { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (value.position + 1 + 4 + 1); + } else if(value instanceof Symbol || value['_bsontype'] == 'Symbol') { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + ((!isBuffer ? numberOfBytes(value.value) : Buffer.byteLength(value.value, 'utf8')) + 4 + 1 + 1); + } else if(value instanceof DBRef || value['_bsontype'] == 'DBRef') { + // Set up correct object for serialization + var ordered_values = { + '$ref': value.namespace + , '$id' : value.oid + }; + + // Add db reference if it exists + if(null != value.db) { + ordered_values['$db'] = value.db; + } + + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + BSON.calculateObjectSize(ordered_values, serializeFunctions); + } else if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]') { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + (!isBuffer ? numberOfBytes(value.source) : Buffer.byteLength(value.source, 'utf8')) + 1 + + (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) + 1 + } else { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + BSON.calculateObjectSize(value, serializeFunctions) + 1; + } + case 'function': + // WTF for 0.4.X where typeof /someregexp/ === 'function' + if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]' || String.call(value) == '[object RegExp]') { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + (!isBuffer ? numberOfBytes(value.source) : Buffer.byteLength(value.source, 'utf8')) + 1 + + (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) + 1 + } else { + if(serializeFunctions && value.scope != null && Object.keys(value.scope).length > 0) { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + 4 + (!isBuffer ? numberOfBytes(value.toString()) : Buffer.byteLength(value.toString(), 'utf8')) + 1 + BSON.calculateObjectSize(value.scope, serializeFunctions); + } else if(serializeFunctions) { + return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + (!isBuffer ? numberOfBytes(value.toString()) : Buffer.byteLength(value.toString(), 'utf8')) + 1; + } + } + } + + return 0; +} + +/** + * Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization. + * + * @param {Object} object the Javascript object to serialize. + * @param {Boolean} checkKeys the serializer will check if keys are valid. + * @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object. + * @param {Number} index the index in the buffer where we wish to start serializing into. + * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. + * @return {Number} returns the new write index in the Buffer. + * @api public + */ +BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, checkKeys, buffer, index, serializeFunctions) { + // Default setting false + serializeFunctions = serializeFunctions == null ? false : serializeFunctions; + // Write end information (length of the object) + var size = buffer.length; + // Write the size of the object + buffer[index++] = size & 0xff; + buffer[index++] = (size >> 8) & 0xff; + buffer[index++] = (size >> 16) & 0xff; + buffer[index++] = (size >> 24) & 0xff; + return serializeObject(object, checkKeys, buffer, index, serializeFunctions) - 1; +} + +/** + * @ignore + * @api private + */ +var serializeObject = function(object, checkKeys, buffer, index, serializeFunctions) { + // Process the object + if(Array.isArray(object)) { + for(var i = 0; i < object.length; i++) { + index = packElement(i.toString(), object[i], checkKeys, buffer, index, serializeFunctions); + } + } else { + for(var key in object) { + // Check the key and throw error if it's illegal + if(checkKeys == true && (key != '$db' && key != '$ref' && key != '$id')) { + BSON.checkKey(key); + } + + // Pack the element + index = packElement(key, object[key], checkKeys, buffer, index, serializeFunctions); + } + } + + // Write zero + buffer[index++] = 0; + return index; +} + +var stringToBytes = function(str) { + var ch, st, re = []; + for (var i = 0; i < str.length; i++ ) { + ch = str.charCodeAt(i); // get char + st = []; // set up "stack" + do { + st.push( ch & 0xFF ); // push byte to stack + ch = ch >> 8; // shift value down by 1 byte + } + while ( ch ); + // add stack contents to result + // done because chars have "wrong" endianness + re = re.concat( st.reverse() ); + } + // return an array of bytes + return re; +} + +var numberOfBytes = function(str) { + var ch, st, re = 0; + for (var i = 0; i < str.length; i++ ) { + ch = str.charCodeAt(i); // get char + st = []; // set up "stack" + do { + st.push( ch & 0xFF ); // push byte to stack + ch = ch >> 8; // shift value down by 1 byte + } + while ( ch ); + // add stack contents to result + // done because chars have "wrong" endianness + re = re + st.length; + } + // return an array of bytes + return re; +} + +/** + * @ignore + * @api private + */ +var writeToTypedArray = function(buffer, string, index) { + var bytes = stringToBytes(string); + for(var i = 0; i < bytes.length; i++) { + buffer[index + i] = bytes[i]; + } + return bytes.length; +} + +/** + * @ignore + * @api private + */ +var supportsBuffer = typeof Buffer != 'undefined'; + +/** + * @ignore + * @api private + */ +var packElement = function(name, value, checkKeys, buffer, index, serializeFunctions) { + var startIndex = index; + + switch(typeof value) { + case 'string': + // Encode String type + buffer[index++] = BSON.BSON_DATA_STRING; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + + // Calculate size + var size = supportsBuffer ? Buffer.byteLength(value) + 1 : numberOfBytes(value) + 1; + // Write the size of the string to buffer + buffer[index + 3] = (size >> 24) & 0xff; + buffer[index + 2] = (size >> 16) & 0xff; + buffer[index + 1] = (size >> 8) & 0xff; + buffer[index] = size & 0xff; + // Ajust the index + index = index + 4; + // Write the string + supportsBuffer ? buffer.write(value, index, 'utf8') : writeToTypedArray(buffer, value, index); + // Update index + index = index + size - 1; + // Write zero + buffer[index++] = 0; + // Return index + return index; + case 'number': + // We have an integer value + if(Math.floor(value) === value && value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { + // If the value fits in 32 bits encode as int, if it fits in a double + // encode it as a double, otherwise long + if(value >= BSON.BSON_INT32_MIN && value <= BSON.BSON_INT32_MAX) { + // Set int type 32 bits or less + buffer[index++] = BSON.BSON_DATA_INT; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Write the int value + buffer[index++] = value & 0xff; + buffer[index++] = (value >> 8) & 0xff; + buffer[index++] = (value >> 16) & 0xff; + buffer[index++] = (value >> 24) & 0xff; + } else if(value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { + // Encode as double + buffer[index++] = BSON.BSON_DATA_NUMBER; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Write float + writeIEEE754(buffer, value, index, 'little', 52, 8); + // Ajust index + index = index + 8; + } else { + // Set long type + buffer[index++] = BSON.BSON_DATA_LONG; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + var longVal = Long.fromNumber(value); + var lowBits = longVal.getLowBits(); + var highBits = longVal.getHighBits(); + // Encode low bits + buffer[index++] = lowBits & 0xff; + buffer[index++] = (lowBits >> 8) & 0xff; + buffer[index++] = (lowBits >> 16) & 0xff; + buffer[index++] = (lowBits >> 24) & 0xff; + // Encode high bits + buffer[index++] = highBits & 0xff; + buffer[index++] = (highBits >> 8) & 0xff; + buffer[index++] = (highBits >> 16) & 0xff; + buffer[index++] = (highBits >> 24) & 0xff; + } + } else { + // Encode as double + buffer[index++] = BSON.BSON_DATA_NUMBER; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Write float + writeIEEE754(buffer, value, index, 'little', 52, 8); + // Ajust index + index = index + 8; + } + + return index; + case 'undefined': + // Set long type + buffer[index++] = BSON.BSON_DATA_NULL; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + return index; + case 'boolean': + // Write the type + buffer[index++] = BSON.BSON_DATA_BOOLEAN; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Encode the boolean value + buffer[index++] = value ? 1 : 0; + return index; + case 'object': + if(value === null || value instanceof MinKey || value instanceof MaxKey + || value['_bsontype'] == 'MinKey' || value['_bsontype'] == 'MaxKey') { + // Write the type of either min or max key + if(value === null) { + buffer[index++] = BSON.BSON_DATA_NULL; + } else if(value instanceof MinKey) { + buffer[index++] = BSON.BSON_DATA_MIN_KEY; + } else { + buffer[index++] = BSON.BSON_DATA_MAX_KEY; + } + + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + return index; + } else if(value instanceof ObjectID || value['_bsontype'] == 'ObjectID') { + // Write the type + buffer[index++] = BSON.BSON_DATA_OID; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + + // Write objectid + supportsBuffer ? buffer.write(value.id, index, 'binary') : writeToTypedArray(buffer, value.id, index); + // Ajust index + index = index + 12; + return index; + } else if(value instanceof Date) { + // Write the type + buffer[index++] = BSON.BSON_DATA_DATE; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + + // Write the date + var dateInMilis = Long.fromNumber(value.getTime()); + var lowBits = dateInMilis.getLowBits(); + var highBits = dateInMilis.getHighBits(); + // Encode low bits + buffer[index++] = lowBits & 0xff; + buffer[index++] = (lowBits >> 8) & 0xff; + buffer[index++] = (lowBits >> 16) & 0xff; + buffer[index++] = (lowBits >> 24) & 0xff; + // Encode high bits + buffer[index++] = highBits & 0xff; + buffer[index++] = (highBits >> 8) & 0xff; + buffer[index++] = (highBits >> 16) & 0xff; + buffer[index++] = (highBits >> 24) & 0xff; + return index; + } else if(typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + // Write the type + buffer[index++] = BSON.BSON_DATA_BINARY; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Get size of the buffer (current write point) + var size = value.length; + // Write the size of the string to buffer + buffer[index++] = size & 0xff; + buffer[index++] = (size >> 8) & 0xff; + buffer[index++] = (size >> 16) & 0xff; + buffer[index++] = (size >> 24) & 0xff; + // Write the default subtype + buffer[index++] = BSON.BSON_BINARY_SUBTYPE_DEFAULT; + // Copy the content form the binary field to the buffer + value.copy(buffer, index, 0, size); + // Adjust the index + index = index + size; + return index; + } else if(value instanceof Long || value instanceof Timestamp || value['_bsontype'] == 'Long' || value['_bsontype'] == 'Timestamp') { + // Write the type + buffer[index++] = value instanceof Long ? BSON.BSON_DATA_LONG : BSON.BSON_DATA_TIMESTAMP; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Write the date + var lowBits = value.getLowBits(); + var highBits = value.getHighBits(); + // Encode low bits + buffer[index++] = lowBits & 0xff; + buffer[index++] = (lowBits >> 8) & 0xff; + buffer[index++] = (lowBits >> 16) & 0xff; + buffer[index++] = (lowBits >> 24) & 0xff; + // Encode high bits + buffer[index++] = highBits & 0xff; + buffer[index++] = (highBits >> 8) & 0xff; + buffer[index++] = (highBits >> 16) & 0xff; + buffer[index++] = (highBits >> 24) & 0xff; + return index; + } else if(value instanceof Double || value['_bsontype'] == 'Double') { + // Encode as double + buffer[index++] = BSON.BSON_DATA_NUMBER; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Write float + writeIEEE754(buffer, value, index, 'little', 52, 8); + // Ajust index + index = index + 8; + return index; + } else if(value instanceof Code || value['_bsontype'] == 'Code') { + if(value.scope != null && Object.keys(value.scope).length > 0) { + // Write the type + buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Calculate the scope size + var scopeSize = BSON.calculateObjectSize(value.scope, serializeFunctions); + // Function string + var functionString = value.code.toString(); + // Function Size + var codeSize = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; + + // Calculate full size of the object + var totalSize = 4 + codeSize + scopeSize + 4; + + // Write the total size of the object + buffer[index++] = totalSize & 0xff; + buffer[index++] = (totalSize >> 8) & 0xff; + buffer[index++] = (totalSize >> 16) & 0xff; + buffer[index++] = (totalSize >> 24) & 0xff; + + // Write the size of the string to buffer + buffer[index++] = codeSize & 0xff; + buffer[index++] = (codeSize >> 8) & 0xff; + buffer[index++] = (codeSize >> 16) & 0xff; + buffer[index++] = (codeSize >> 24) & 0xff; + + // Write the string + supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); + // Update index + index = index + codeSize - 1; + // Write zero + buffer[index++] = 0; + // Serialize the scope object + var scopeObjectBuffer = supportsBuffer ? new Buffer(scopeSize) : new Uint8Array(new ArrayBuffer(scopeSize)); + // Execute the serialization into a seperate buffer + serializeObject(value.scope, checkKeys, scopeObjectBuffer, 0, serializeFunctions); + + // Adjusted scope Size (removing the header) + var scopeDocSize = scopeSize; + // Write scope object size + buffer[index++] = scopeDocSize & 0xff; + buffer[index++] = (scopeDocSize >> 8) & 0xff; + buffer[index++] = (scopeDocSize >> 16) & 0xff; + buffer[index++] = (scopeDocSize >> 24) & 0xff; + + // Write the scopeObject into the buffer + supportsBuffer ? scopeObjectBuffer.copy(buffer, index, 0, scopeSize) : buffer.set(scopeObjectBuffer, index); + // Adjust index, removing the empty size of the doc (5 bytes 0000000005) + index = index + scopeDocSize - 5; + // Write trailing zero + buffer[index++] = 0; + return index + } else { + buffer[index++] = BSON.BSON_DATA_CODE; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Function string + var functionString = value.code.toString(); + // Function Size + var size = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; + // Write the size of the string to buffer + buffer[index++] = size & 0xff; + buffer[index++] = (size >> 8) & 0xff; + buffer[index++] = (size >> 16) & 0xff; + buffer[index++] = (size >> 24) & 0xff; + // Write the string + buffer.write(functionString, index, 'utf8'); + // Update index + index = index + size - 1; + // Write zero + buffer[index++] = 0; + return index; + } + } else if(value instanceof Binary || value['_bsontype'] == 'Binary') { + // Write the type + buffer[index++] = BSON.BSON_DATA_BINARY; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Extract the buffer + var data = value.value(true); + // Calculate size + var size = value.position; + // Write the size of the string to buffer + buffer[index++] = size & 0xff; + buffer[index++] = (size >> 8) & 0xff; + buffer[index++] = (size >> 16) & 0xff; + buffer[index++] = (size >> 24) & 0xff; + // Write the subtype to the buffer + buffer[index++] = value.sub_type; + // Write the data to the object + supportsBuffer ? data.copy(buffer, index, 0, value.position) : buffer.set(data, index); + // Ajust index + index = index + value.position; + return index; + } else if(value instanceof Symbol || value['_bsontype'] == 'Symbol') { + // Write the type + buffer[index++] = BSON.BSON_DATA_SYMBOL; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Calculate size + var size = supportsBuffer ? Buffer.byteLength(value.value) + 1 : numberOfBytes(value.value) + 1; + // Write the size of the string to buffer + buffer[index++] = size & 0xff; + buffer[index++] = (size >> 8) & 0xff; + buffer[index++] = (size >> 16) & 0xff; + buffer[index++] = (size >> 24) & 0xff; + // Write the string + buffer.write(value.value, index, 'utf8'); + // Update index + index = index + size - 1; + // Write zero + buffer[index++] = 0x00; + return index; + } else if(value instanceof DBRef || value['_bsontype'] == 'DBRef') { + // Write the type + buffer[index++] = BSON.BSON_DATA_OBJECT; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Set up correct object for serialization + var ordered_values = { + '$ref': value.namespace + , '$id' : value.oid + }; + + // Add db reference if it exists + if(null != value.db) { + ordered_values['$db'] = value.db; + } + + // Message size + var size = BSON.calculateObjectSize(ordered_values, serializeFunctions); + // Serialize the object + var endIndex = BSON.serializeWithBufferAndIndex(ordered_values, checkKeys, buffer, index, serializeFunctions); + // Write the size of the string to buffer + buffer[index++] = size & 0xff; + buffer[index++] = (size >> 8) & 0xff; + buffer[index++] = (size >> 16) & 0xff; + buffer[index++] = (size >> 24) & 0xff; + // Write zero for object + buffer[endIndex++] = 0x00; + // Return the end index + return endIndex; + } else if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]') { + // Write the type + buffer[index++] = BSON.BSON_DATA_REGEXP; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + + // Write the regular expression string + supportsBuffer ? buffer.write(value.source, index, 'utf8') : writeToTypedArray(buffer, value.source, index); + // Adjust the index + index = index + (supportsBuffer ? Buffer.byteLength(value.source) : numberOfBytes(value.source)); + // Write zero + buffer[index++] = 0x00; + // Write the parameters + if(value.global) buffer[index++] = 0x73; // s + if(value.ignoreCase) buffer[index++] = 0x69; // i + if(value.multiline) buffer[index++] = 0x6d; // m + // Add ending zero + buffer[index++] = 0x00; + return index; + } else { + // Write the type + buffer[index++] = Array.isArray(value) ? BSON.BSON_DATA_ARRAY : BSON.BSON_DATA_OBJECT; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Adjust the index + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Serialize the object + var endIndex = serializeObject(value, checkKeys, buffer, index + 4, serializeFunctions); + // Write size + var size = endIndex - index; + // Write the size of the string to buffer + buffer[index++] = size & 0xff; + buffer[index++] = (size >> 8) & 0xff; + buffer[index++] = (size >> 16) & 0xff; + buffer[index++] = (size >> 24) & 0xff; + return endIndex; + } + case 'function': + // WTF for 0.4.X where typeof /someregexp/ === 'function' + if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]' || String.call(value) == '[object RegExp]') { + // Write the type + buffer[index++] = BSON.BSON_DATA_REGEXP; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + + // Write the regular expression string + buffer.write(value.source, index, 'utf8'); + // Adjust the index + index = index + (supportsBuffer ? Buffer.byteLength(value.source) : numberOfBytes(value.source)); + // Write zero + buffer[index++] = 0x00; + // Write the parameters + if(value.global) buffer[index++] = 0x73; // s + if(value.ignoreCase) buffer[index++] = 0x69; // i + if(value.multiline) buffer[index++] = 0x6d; // m + // Add ending zero + buffer[index++] = 0x00; + return index; + } else { + if(serializeFunctions && value.scope != null && Object.keys(value.scope).length > 0) { + // Write the type + buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Calculate the scope size + var scopeSize = BSON.calculateObjectSize(value.scope, serializeFunctions); + // Function string + var functionString = value.toString(); + // Function Size + var codeSize = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; + + // Calculate full size of the object + var totalSize = 4 + codeSize + scopeSize; + + // Write the total size of the object + buffer[index++] = totalSize & 0xff; + buffer[index++] = (totalSize >> 8) & 0xff; + buffer[index++] = (totalSize >> 16) & 0xff; + buffer[index++] = (totalSize >> 24) & 0xff; + + // Write the size of the string to buffer + buffer[index++] = codeSize & 0xff; + buffer[index++] = (codeSize >> 8) & 0xff; + buffer[index++] = (codeSize >> 16) & 0xff; + buffer[index++] = (codeSize >> 24) & 0xff; + + // Write the string + buffer.write(functionString, index, 'utf8'); + // Update index + index = index + codeSize - 1; + // Write zero + buffer[index++] = 0; + // Serialize the scope object + var scopeObjectBuffer = new Buffer(scopeSize); + // Execute the serialization into a seperate buffer + serializeObject(value.scope, checkKeys, scopeObjectBuffer, 0, serializeFunctions); + + // Adjusted scope Size (removing the header) + var scopeDocSize = scopeSize - 4; + // Write scope object size + buffer[index++] = scopeDocSize & 0xff; + buffer[index++] = (scopeDocSize >> 8) & 0xff; + buffer[index++] = (scopeDocSize >> 16) & 0xff; + buffer[index++] = (scopeDocSize >> 24) & 0xff; + + // Write the scopeObject into the buffer + scopeObjectBuffer.copy(buffer, index, 0, scopeSize); + + // Adjust index, removing the empty size of the doc (5 bytes 0000000005) + index = index + scopeDocSize - 5; + // Write trailing zero + buffer[index++] = 0; + return index + } else if(serializeFunctions) { + buffer[index++] = BSON.BSON_DATA_CODE; + // Number of written bytes + var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); + // Encode the name + index = index + numberOfWrittenBytes + 1; + buffer[index - 1] = 0; + // Function string + var functionString = value.toString(); + // Function Size + var size = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; + // Write the size of the string to buffer + buffer[index++] = size & 0xff; + buffer[index++] = (size >> 8) & 0xff; + buffer[index++] = (size >> 16) & 0xff; + buffer[index++] = (size >> 24) & 0xff; + // Write the string + buffer.write(functionString, index, 'utf8'); + // Update index + index = index + size - 1; + // Write zero + buffer[index++] = 0; + return index; + } + } + } + + // If no value to serialize + return index; +} + +/** + * Serialize a Javascript object. + * + * @param {Object} object the Javascript object to serialize. + * @param {Boolean} checkKeys the serializer will check if keys are valid. + * @param {Boolean} asBuffer return the serialized object as a Buffer object **(ignore)**. + * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. + * @return {Buffer} returns the Buffer object containing the serialized object. + * @api public + */ +BSON.serialize = function(object, checkKeys, asBuffer, serializeFunctions) { + var buffer = null; + // Calculate the size of the object + var size = BSON.calculateObjectSize(object, serializeFunctions); + // Fetch the best available type for storing the binary data + if(buffer = typeof Buffer != 'undefined') { + buffer = new Buffer(size); + asBuffer = true; + } else if(typeof Uint8Array != 'undefined') { + buffer = new Uint8Array(new ArrayBuffer(size)); + } else { + buffer = new Array(size); + } + + // If asBuffer is false use typed arrays + BSON.serializeWithBufferAndIndex(object, checkKeys, buffer, 0, serializeFunctions); + return buffer; +} + +/** + * Contains the function cache if we have that enable to allow for avoiding the eval step on each deserialization, comparison is by md5 + * + * @ignore + * @api private + */ +var functionCache = BSON.functionCache = {}; + +/** + * Crc state variables shared by function + * + * @ignore + * @api private + */ +var table = [0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D]; + +/** + * CRC32 hash method, Fast and enough versitility for our usage + * + * @ignore + * @api private + */ +var crc32 = function(string, start, end) { + var crc = 0 + var x = 0; + var y = 0; + crc = crc ^ (-1); + + for(var i = start, iTop = end; i < iTop;i++) { + y = (crc ^ string[i]) & 0xFF; + x = table[y]; + crc = (crc >>> 8) ^ x; + } + + return crc ^ (-1); +} + +/** + * Deserialize stream data as BSON documents. + * + * Options + * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. + * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. + * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. + * + * @param {Buffer} data the buffer containing the serialized set of BSON documents. + * @param {Number} startIndex the start index in the data Buffer where the deserialization is to start. + * @param {Number} numberOfDocuments number of documents to deserialize. + * @param {Array} documents an array where to store the deserialized documents. + * @param {Number} docStartIndex the index in the documents array from where to start inserting documents. + * @param {Object} [options] additional options used for the deserialization. + * @return {Number} returns the next index in the buffer after deserialization **x** numbers of documents. + * @api public + */ +BSON.deserializeStream = function(data, startIndex, numberOfDocuments, documents, docStartIndex, options) { + // if(numberOfDocuments !== documents.length) throw new Error("Number of expected results back is less than the number of documents"); + options = options != null ? options : {}; + var index = startIndex; + // Loop over all documents + for(var i = 0; i < numberOfDocuments; i++) { + // Find size of the document + var size = data[index] | data[index + 1] << 8 | data[index + 2] << 16 | data[index + 3] << 24; + // Update options with index + options['index'] = index; + // Parse the document at this point + documents[docStartIndex + i] = BSON.deserialize(data, options); + // Adjust index by the document size + index = index + size; + } + + // Return object containing end index of parsing and list of documents + return index; +} + +/** + * Ensure eval is isolated. + * + * @ignore + * @api private + */ +var isolateEvalWithHash = function(functionCache, hash, functionString, object) { + // Contains the value we are going to set + var value = null; + + // Check for cache hit, eval if missing and return cached function + if(functionCache[hash] == null) { + eval("value = " + functionString); + functionCache[hash] = value; + } + // Set the object + return functionCache[hash].bind(object); +} + +/** + * Ensure eval is isolated. + * + * @ignore + * @api private + */ +var isolateEval = function(functionString) { + // Contains the value we are going to set + var value = null; + // Eval the function + eval("value = " + functionString); + return value; +} + +/** + * Convert Uint8Array to String + * + * @ignore + * @api private + */ +var convertUint8ArrayToUtf8String = function(byteArray, startIndex, endIndex) { + return BinaryParser.decode_utf8(convertArraytoUtf8BinaryString(byteArray, startIndex, endIndex)); +} + +var convertArraytoUtf8BinaryString = function(byteArray, startIndex, endIndex) { + var result = ""; + for(var i = startIndex; i < endIndex; i++) { + result = result + String.fromCharCode(byteArray[i]); + } + + return result; +}; + +/** + * Deserialize data as BSON. + * + * Options + * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. + * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. + * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. + * + * @param {Buffer} buffer the buffer containing the serialized set of BSON documents. + * @param {Object} [options] additional options used for the deserialization. + * @param {Boolean} [isArray] ignore used for recursive parsing. + * @return {Object} returns the deserialized Javascript Object. + * @api public + */ +BSON.deserialize = function(buffer, options, isArray) { + // Options + options = options == null ? {} : options; + var evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions']; + var cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions']; + var cacheFunctionsCrc32 = options['cacheFunctionsCrc32'] == null ? false : options['cacheFunctionsCrc32']; + + // Validate that we have at least 4 bytes of buffer + if(buffer.length < 5) throw new Error("corrupt bson message < 5 bytes long"); + + // Set up index + var index = typeof options['index'] == 'number' ? options['index'] : 0; + // Reads in a C style string + var readCStyleString = function() { + // Get the start search index + var i = index; + // Locate the end of the c string + while(buffer[i] !== 0x00) { i++ } + // Grab utf8 encoded string + var string = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, i) : convertUint8ArrayToUtf8String(buffer, index, i); + // Update index position + index = i + 1; + // Return string + return string; + } + + // Create holding object + var object = isArray ? [] : {}; + + // Read the document size + var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + + // Ensure buffer is valid size + if(size < 5 || size > buffer.length) throw new Error("corrupt bson message"); + + // While we have more left data left keep parsing + while(true) { + // Read the type + var elementType = buffer[index++]; + // If we get a zero it's the last byte, exit + if(elementType == 0) break; + // Read the name of the field + var name = readCStyleString(); + // Switch on the type + switch(elementType) { + case BSON.BSON_DATA_OID: + var string = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('binary', index, index + 12) : convertArraytoUtf8BinaryString(buffer, index, index + 12); + // Decode the oid + object[name] = new ObjectID(string); + // Update index + index = index + 12; + break; + case BSON.BSON_DATA_STRING: + // Read the content of the field + var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + // Add string to object + object[name] = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); + // Update parse index position + index = index + stringSize; + break; + case BSON.BSON_DATA_INT: + // Decode the 32bit value + object[name] = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + break; + case BSON.BSON_DATA_NUMBER: + // Decode the double value + object[name] = readIEEE754(buffer, index, 'little', 52, 8); + // Update the index + index = index + 8; + break; + case BSON.BSON_DATA_DATE: + // Unpack the low and high bits + var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + // Set date object + object[name] = new Date(new Long(lowBits, highBits).toNumber()); + break; + case BSON.BSON_DATA_BOOLEAN: + // Parse the boolean value + object[name] = buffer[index++] == 1; + break; + case BSON.BSON_DATA_NULL: + // Parse the boolean value + object[name] = null; + break; + case BSON.BSON_DATA_BINARY: + // Decode the size of the binary blob + var binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + // Decode the subtype + var subType = buffer[index++]; + // Decode as raw Buffer object if options specifies it + if(buffer['slice'] != null) { + object[name] = new Binary(buffer.slice(index, index + binarySize), subType); + } else { + var _buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(binarySize)) : new Array(binarySize); + for(var i = 0; i < binarySize; i++) { + _buffer[i] = buffer[index + i]; + } + // Create the binary object + object[name] = new Binary(_buffer, subType); + } + // Update the index + index = index + binarySize; + break; + case BSON.BSON_DATA_ARRAY: + options['index'] = index; + // Decode the size of the array document + var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; + // Set the array to the object + object[name] = BSON.deserialize(buffer, options, true); + // Adjust the index + index = index + objectSize; + break; + case BSON.BSON_DATA_OBJECT: + options['index'] = index; + // Decode the size of the object document + var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; + // Set the array to the object + object[name] = BSON.deserialize(buffer, options, false); + // Adjust the index + index = index + objectSize; + break; + case BSON.BSON_DATA_REGEXP: + // Create the regexp + var source = readCStyleString(); + var regExpOptions = readCStyleString(); + // For each option add the corresponding one for javascript + var optionsArray = new Array(regExpOptions.length); + + // Parse options + for(var i = 0; i < regExpOptions.length; i++) { + switch(regExpOptions[i]) { + case 'm': + optionsArray[i] = 'm'; + break; + case 's': + optionsArray[i] = 'g'; + break; + case 'i': + optionsArray[i] = 'i'; + break; + } + } + + object[name] = new RegExp(source, optionsArray.join('')); + break; + case BSON.BSON_DATA_LONG: + // Unpack the low and high bits + var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + // Create long object + var long = new Long(lowBits, highBits); + // Set the object + object[name] = long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG) ? long.toNumber() : long; + break; + case BSON.BSON_DATA_SYMBOL: + // Read the content of the field + var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + // Add string to object + object[name] = new Symbol(buffer.toString('utf8', index, index + stringSize - 1)); + // Update parse index position + index = index + stringSize; + break; + case BSON.BSON_DATA_TIMESTAMP: + // Unpack the low and high bits + var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + // Set the object + object[name] = new Timestamp(lowBits, highBits); + break; + case BSON.BSON_DATA_MIN_KEY: + // Parse the object + object[name] = new MinKey(); + break; + case BSON.BSON_DATA_MAX_KEY: + // Parse the object + object[name] = new MaxKey(); + break; + case BSON.BSON_DATA_CODE: + // Read the content of the field + var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + // Function string + var functionString = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); + + // If we are evaluating the functions + if(evalFunctions) { + // Contains the value we are going to set + var value = null; + // If we have cache enabled let's look for the md5 of the function in the cache + if(cacheFunctions) { + var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString; + // Got to do this to avoid V8 deoptimizing the call due to finding eval + object[name] = isolateEvalWithHash(functionCache, hash, functionString, object); + } else { + // Set directly + object[name] = isolateEval(functionString); + } + } else { + object[name] = new Code(functionString, {}); + } + + // Update parse index position + index = index + stringSize; + break; + case BSON.BSON_DATA_CODE_W_SCOPE: + // Read the content of the field + var totalSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; + // Javascript function + var functionString = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); + // Update parse index position + index = index + stringSize; + // Parse the element + options['index'] = index; + // Decode the size of the object document + var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; + // Decode the scope object + var scopeObject = BSON.deserialize(buffer, options, false); + // Adjust the index + index = index + objectSize; + + // If we are evaluating the functions + if(evalFunctions) { + // Contains the value we are going to set + var value = null; + // If we have cache enabled let's look for the md5 of the function in the cache + if(cacheFunctions) { + var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString; + // Got to do this to avoid V8 deoptimizing the call due to finding eval + object[name] = isolateEvalWithHash(functionCache, hash, functionString, object); + } else { + // Set directly + object[name] = isolateEval(functionString); + } + + // Set the scope on the object + object[name].scope = scopeObject; + } else { + object[name] = new Code(functionString, scopeObject); + } + + // Add string to object + break; + } + } + + // Check if we have a db ref object + if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']); + + // Return the final objects + return object; +} + +/** + * Check if key name is valid. + * + * @ignore + * @api private + */ +BSON.checkKey = function checkKey (key) { + if (!key.length) return; + // Check if we have a legal key for the object + if('$' == key[0]) { + throw Error("key " + key + " must not start with '$'"); + } else if (!!~key.indexOf('.')) { + throw Error("key " + key + " must not contain '.'"); + } +}; + +/** + * Deserialize data as BSON. + * + * Options + * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. + * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. + * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. + * + * @param {Buffer} buffer the buffer containing the serialized set of BSON documents. + * @param {Object} [options] additional options used for the deserialization. + * @param {Boolean} [isArray] ignore used for recursive parsing. + * @return {Object} returns the deserialized Javascript Object. + * @api public + */ +BSON.prototype.deserialize = function(data, options) { + return BSON.deserialize(data, options); +} + +/** + * Deserialize stream data as BSON documents. + * + * Options + * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. + * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. + * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. + * + * @param {Buffer} data the buffer containing the serialized set of BSON documents. + * @param {Number} startIndex the start index in the data Buffer where the deserialization is to start. + * @param {Number} numberOfDocuments number of documents to deserialize. + * @param {Array} documents an array where to store the deserialized documents. + * @param {Number} docStartIndex the index in the documents array from where to start inserting documents. + * @param {Object} [options] additional options used for the deserialization. + * @return {Number} returns the next index in the buffer after deserialization **x** numbers of documents. + * @api public + */ +BSON.prototype.deserializeStream = function(data, startIndex, numberOfDocuments, documents, docStartIndex, options) { + return BSON.deserializeStream(data, startIndex, numberOfDocuments, documents, docStartIndex, options); +} + +/** + * Serialize a Javascript object. + * + * @param {Object} object the Javascript object to serialize. + * @param {Boolean} checkKeys the serializer will check if keys are valid. + * @param {Boolean} asBuffer return the serialized object as a Buffer object **(ignore)**. + * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. + * @return {Buffer} returns the Buffer object containing the serialized object. + * @api public + */ +BSON.prototype.serialize = function(object, checkKeys, asBuffer, serializeFunctions) { + return BSON.serialize(object, checkKeys, asBuffer, serializeFunctions); +} + +/** + * Calculate the bson size for a passed in Javascript object. + * + * @param {Object} object the Javascript object to calculate the BSON byte size for. + * @param {Boolean} [serializeFunctions] serialize all functions in the object **(default:false)**. + * @return {Number} returns the number of bytes the BSON object will take up. + * @api public + */ +BSON.prototype.calculateObjectSize = function(object, serializeFunctions) { + return BSON.calculateObjectSize(object, serializeFunctions); +} + +/** + * Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization. + * + * @param {Object} object the Javascript object to serialize. + * @param {Boolean} checkKeys the serializer will check if keys are valid. + * @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object. + * @param {Number} index the index in the buffer where we wish to start serializing into. + * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. + * @return {Number} returns the new write index in the Buffer. + * @api public + */ +BSON.prototype.serializeWithBufferAndIndex = function(object, checkKeys, buffer, startIndex, serializeFunctions) { + return BSON.serializeWithBufferAndIndex(object, checkKeys, buffer, startIndex, serializeFunctions); +} + +/** + * @ignore + * @api private + */ +if(typeof window === 'undefined') { + exports.Code = Code; + exports.Symbol = Symbol; + exports.BSON = BSON; + exports.DBRef = DBRef; + exports.Binary = Binary; + exports.ObjectID = ObjectID; + exports.Long = Long; + exports.Timestamp = Timestamp; + exports.Double = Double; + exports.MinKey = MinKey; + exports.MaxKey = MaxKey; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/code.js b/node_modules/mongodb/node_modules/bson/lib/bson/code.js new file mode 100644 index 0000000..c15c776 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/code.js @@ -0,0 +1,27 @@ +/** + * A class representation of the BSON Code type. + * + * @class Represents the BSON Code type. + * @param {String|Function} code a string or function. + * @param {Object} [scope] an optional scope for the function. + * @return {Code} + */ +function Code(code, scope) { + if(!(this instanceof Code)) return new Code(code, scope); + + this._bsontype = 'Code'; + this.code = code; + this.scope = scope == null ? {} : scope; +}; + +/** + * @ignore + * @api private + */ +Code.prototype.toJSON = function() { + return {scope:this.scope, code:this.code}; +} + +if(typeof window === 'undefined') { + exports.Code = Code; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/db_ref.js b/node_modules/mongodb/node_modules/bson/lib/bson/db_ref.js new file mode 100644 index 0000000..5e5e33b --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/db_ref.js @@ -0,0 +1,33 @@ +/** + * A class representation of the BSON DBRef type. + * + * @class Represents the BSON DBRef type. + * @param {String} namespace the collection name. + * @param {ObjectID} oid the reference ObjectID. + * @param {String} [db] optional db name, if omitted the reference is local to the current db. + * @return {DBRef} + */ +function DBRef(namespace, oid, db) { + if(!(this instanceof DBRef)) return new DBRef(namespace, oid, db); + + this._bsontype = 'DBRef'; + this.namespace = namespace; + this.oid = oid; + this.db = db; +}; + +/** + * @ignore + * @api private + */ +DBRef.prototype.toJSON = function() { + return { + '$ref':this.namespace, + '$id':this.oid, + '$db':this.db == null ? '' : this.db + }; +} + +if(typeof window === 'undefined') { + exports.DBRef = DBRef; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/double.js b/node_modules/mongodb/node_modules/bson/lib/bson/double.js new file mode 100644 index 0000000..b1e12df --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/double.js @@ -0,0 +1,35 @@ +/** + * A class representation of the BSON Double type. + * + * @class Represents the BSON Double type. + * @param {Number} value the number we want to represent as a double. + * @return {Double} + */ +function Double(value) { + if(!(this instanceof Double)) return new Double(value); + + this._bsontype = 'Double'; + this.value = value; +} + +/** + * Access the number value. + * + * @return {Number} returns the wrapped double number. + * @api public + */ +Double.prototype.valueOf = function() { + return this.value; +}; + +/** + * @ignore + * @api private + */ +Double.prototype.toJSON = function() { + return this.value; +} + +if(typeof window === 'undefined') { + exports.Double = Double; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/float_parser.js b/node_modules/mongodb/node_modules/bson/lib/bson/float_parser.js new file mode 100644 index 0000000..7c12fe5 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/float_parser.js @@ -0,0 +1,123 @@ +// Copyright (c) 2008, Fair Oaks Labs, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// Modifications to writeIEEE754 to support negative zeroes made by Brian White + +var readIEEE754 = function(buffer, offset, endian, mLen, nBytes) { + var e, m, + bBE = (endian === 'big'), + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + nBits = -7, + i = bBE ? 0 : (nBytes - 1), + d = bBE ? 1 : -1, + s = buffer[offset + i]; + + i += d; + + e = s & ((1 << (-nBits)) - 1); + s >>= (-nBits); + nBits += eLen; + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); + + m = e & ((1 << (-nBits)) - 1); + e >>= (-nBits); + nBits += mLen; + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); + + if (e === 0) { + e = 1 - eBias; + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity); + } else { + m = m + Math.pow(2, mLen); + e = e - eBias; + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen); +}; + +var writeIEEE754 = function(buffer, value, offset, endian, mLen, nBytes) { + var e, m, c, + bBE = (endian === 'big'), + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), + i = bBE ? (nBytes-1) : 0, + d = bBE ? -1 : 1, + s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; + + value = Math.abs(value); + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0; + e = eMax; + } else { + e = Math.floor(Math.log(value) / Math.LN2); + if (value * (c = Math.pow(2, -e)) < 1) { + e--; + c *= 2; + } + if (e+eBias >= 1) { + value += rt / c; + } else { + value += rt * Math.pow(2, 1 - eBias); + } + if (value * c >= 2) { + e++; + c /= 2; + } + + if (e + eBias >= eMax) { + m = 0; + e = eMax; + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen); + e = e + eBias; + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); + e = 0; + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8); + + e = (e << mLen) | m; + eLen += mLen; + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8); + + buffer[offset + i - d] |= s * 128; +}; + +if(typeof window === 'undefined') { + exports.readIEEE754 = readIEEE754; + exports.writeIEEE754 = writeIEEE754; +} diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/index.js b/node_modules/mongodb/node_modules/bson/lib/bson/index.js new file mode 100644 index 0000000..950fcad --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/index.js @@ -0,0 +1,74 @@ +try { + exports.BSONPure = require('./bson'); + exports.BSONNative = require('../../ext'); +} catch(err) { + // do nothing +} + +[ './binary_parser' + , './binary' + , './code' + , './db_ref' + , './double' + , './max_key' + , './min_key' + , './objectid' + , './symbol' + , './timestamp' + , './long'].forEach(function (path) { + var module = require('./' + path); + for (var i in module) { + exports[i] = module[i]; + } +}); + +// Exports all the classes for the NATIVE JS BSON Parser +exports.native = function() { + var classes = {}; + // Map all the classes + [ './binary_parser' + , './binary' + , './code' + , './db_ref' + , './double' + , './max_key' + , './min_key' + , './objectid' + , './symbol' + , './timestamp' + , './long' + , '../../ext' +].forEach(function (path) { + var module = require('./' + path); + for (var i in module) { + classes[i] = module[i]; + } + }); + // Return classes list + return classes; +} + +// Exports all the classes for the PURE JS BSON Parser +exports.pure = function() { + var classes = {}; + // Map all the classes + [ './binary_parser' + , './binary' + , './code' + , './db_ref' + , './double' + , './max_key' + , './min_key' + , './objectid' + , './symbol' + , './timestamp' + , './long' + , '././bson'].forEach(function (path) { + var module = require('./' + path); + for (var i in module) { + classes[i] = module[i]; + } + }); + // Return classes list + return classes; +} diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/long.js b/node_modules/mongodb/node_modules/bson/lib/bson/long.js new file mode 100644 index 0000000..6aa9749 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/long.js @@ -0,0 +1,856 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright 2009 Google Inc. All Rights Reserved + +/** + * Defines a Long class for representing a 64-bit two's-complement + * integer value, which faithfully simulates the behavior of a Java "Long". This + * implementation is derived from LongLib in GWT. + * + * Constructs a 64-bit two's-complement integer, given its low and high 32-bit + * values as *signed* integers. See the from* functions below for more + * convenient ways of constructing Longs. + * + * The internal representation of a Long is the two given signed, 32-bit values. + * We use 32-bit pieces because these are the size of integers on which + * Javascript performs bit-operations. For operations like addition and + * multiplication, we split each number into 16-bit pieces, which can easily be + * multiplied within Javascript's floating-point representation without overflow + * or change in sign. + * + * In the algorithms below, we frequently reduce the negative case to the + * positive case by negating the input(s) and then post-processing the result. + * Note that we must ALWAYS check specially whether those values are MIN_VALUE + * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as + * a positive number, it overflows back into a negative). Not handling this + * case would often result in infinite recursion. + * + * @class Represents the BSON Long type. + * @param {Number} low the low (signed) 32 bits of the Long. + * @param {Number} high the high (signed) 32 bits of the Long. + */ +function Long(low, high) { + if(!(this instanceof Long)) return new Long(low, high); + + this._bsontype = 'Long'; + /** + * @type {number} + * @api private + */ + this.low_ = low | 0; // force into 32 signed bits. + + /** + * @type {number} + * @api private + */ + this.high_ = high | 0; // force into 32 signed bits. +}; + +/** + * Return the int value. + * + * @return {Number} the value, assuming it is a 32-bit integer. + * @api public + */ +Long.prototype.toInt = function() { + return this.low_; +}; + +/** + * Return the Number value. + * + * @return {Number} the closest floating-point representation to this value. + * @api public + */ +Long.prototype.toNumber = function() { + return this.high_ * Long.TWO_PWR_32_DBL_ + + this.getLowBitsUnsigned(); +}; + +/** + * Return the JSON value. + * + * @return {String} the JSON representation. + * @api public + */ +Long.prototype.toJSON = function() { + return this.toString(); +} + +/** + * Return the String value. + * + * @param {Number} [opt_radix] the radix in which the text should be written. + * @return {String} the textual representation of this value. + * @api public + */ +Long.prototype.toString = function(opt_radix) { + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw Error('radix out of range: ' + radix); + } + + if (this.isZero()) { + return '0'; + } + + if (this.isNegative()) { + if (this.equals(Long.MIN_VALUE)) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixLong = Long.fromNumber(radix); + var div = this.div(radixLong); + var rem = div.multiply(radixLong).subtract(this); + return div.toString(radix) + rem.toInt().toString(radix); + } else { + return '-' + this.negate().toString(radix); + } + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = Long.fromNumber(Math.pow(radix, 6)); + + var rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower); + var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt(); + var digits = intval.toString(radix); + + rem = remDiv; + if (rem.isZero()) { + return digits + result; + } else { + while (digits.length < 6) { + digits = '0' + digits; + } + result = '' + digits + result; + } + } +}; + +/** + * Return the high 32-bits value. + * + * @return {Number} the high 32-bits as a signed value. + * @api public + */ +Long.prototype.getHighBits = function() { + return this.high_; +}; + +/** + * Return the low 32-bits value. + * + * @return {Number} the low 32-bits as a signed value. + * @api public + */ +Long.prototype.getLowBits = function() { + return this.low_; +}; + +/** + * Return the low unsigned 32-bits value. + * + * @return {Number} the low 32-bits as an unsigned value. + * @api public + */ +Long.prototype.getLowBitsUnsigned = function() { + return (this.low_ >= 0) ? + this.low_ : Long.TWO_PWR_32_DBL_ + this.low_; +}; + +/** + * Returns the number of bits needed to represent the absolute value of this Long. + * + * @return {Number} Returns the number of bits needed to represent the absolute value of this Long. + * @api public + */ +Long.prototype.getNumBitsAbs = function() { + if (this.isNegative()) { + if (this.equals(Long.MIN_VALUE)) { + return 64; + } else { + return this.negate().getNumBitsAbs(); + } + } else { + var val = this.high_ != 0 ? this.high_ : this.low_; + for (var bit = 31; bit > 0; bit--) { + if ((val & (1 << bit)) != 0) { + break; + } + } + return this.high_ != 0 ? bit + 33 : bit + 1; + } +}; + +/** + * Return whether this value is zero. + * + * @return {Boolean} whether this value is zero. + * @api public + */ +Long.prototype.isZero = function() { + return this.high_ == 0 && this.low_ == 0; +}; + +/** + * Return whether this value is negative. + * + * @return {Boolean} whether this value is negative. + * @api public + */ +Long.prototype.isNegative = function() { + return this.high_ < 0; +}; + +/** + * Return whether this value is odd. + * + * @return {Boolean} whether this value is odd. + * @api public + */ +Long.prototype.isOdd = function() { + return (this.low_ & 1) == 1; +}; + +/** + * Return whether this Long equals the other + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long equals the other + * @api public + */ +Long.prototype.equals = function(other) { + return (this.high_ == other.high_) && (this.low_ == other.low_); +}; + +/** + * Return whether this Long does not equal the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long does not equal the other. + * @api public + */ +Long.prototype.notEquals = function(other) { + return (this.high_ != other.high_) || (this.low_ != other.low_); +}; + +/** + * Return whether this Long is less than the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long is less than the other. + * @api public + */ +Long.prototype.lessThan = function(other) { + return this.compare(other) < 0; +}; + +/** + * Return whether this Long is less than or equal to the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long is less than or equal to the other. + * @api public + */ +Long.prototype.lessThanOrEqual = function(other) { + return this.compare(other) <= 0; +}; + +/** + * Return whether this Long is greater than the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long is greater than the other. + * @api public + */ +Long.prototype.greaterThan = function(other) { + return this.compare(other) > 0; +}; + +/** + * Return whether this Long is greater than or equal to the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long is greater than or equal to the other. + * @api public + */ +Long.prototype.greaterThanOrEqual = function(other) { + return this.compare(other) >= 0; +}; + +/** + * Compares this Long with the given one. + * + * @param {Long} other Long to compare against. + * @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater. + * @api public + */ +Long.prototype.compare = function(other) { + if (this.equals(other)) { + return 0; + } + + var thisNeg = this.isNegative(); + var otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) { + return -1; + } + if (!thisNeg && otherNeg) { + return 1; + } + + // at this point, the signs are the same, so subtraction will not overflow + if (this.subtract(other).isNegative()) { + return -1; + } else { + return 1; + } +}; + +/** + * The negation of this value. + * + * @return {Long} the negation of this value. + * @api public + */ +Long.prototype.negate = function() { + if (this.equals(Long.MIN_VALUE)) { + return Long.MIN_VALUE; + } else { + return this.not().add(Long.ONE); + } +}; + +/** + * Returns the sum of this and the given Long. + * + * @param {Long} other Long to add to this one. + * @return {Long} the sum of this and the given Long. + * @api public + */ +Long.prototype.add = function(other) { + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xFFFF; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xFFFF; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xFFFF; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); +}; + +/** + * Returns the difference of this and the given Long. + * + * @param {Long} other Long to subtract from this. + * @return {Long} the difference of this and the given Long. + * @api public + */ +Long.prototype.subtract = function(other) { + return this.add(other.negate()); +}; + +/** + * Returns the product of this and the given Long. + * + * @param {Long} other Long to multiply with this. + * @return {Long} the product of this and the other. + * @api public + */ +Long.prototype.multiply = function(other) { + if (this.isZero()) { + return Long.ZERO; + } else if (other.isZero()) { + return Long.ZERO; + } + + if (this.equals(Long.MIN_VALUE)) { + return other.isOdd() ? Long.MIN_VALUE : Long.ZERO; + } else if (other.equals(Long.MIN_VALUE)) { + return this.isOdd() ? Long.MIN_VALUE : Long.ZERO; + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().multiply(other.negate()); + } else { + return this.negate().multiply(other).negate(); + } + } else if (other.isNegative()) { + return this.multiply(other.negate()).negate(); + } + + // If both Longs are small, use float multiplication + if (this.lessThan(Long.TWO_PWR_24_) && + other.lessThan(Long.TWO_PWR_24_)) { + return Long.fromNumber(this.toNumber() * other.toNumber()); + } + + // Divide each Long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xFFFF; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xFFFF; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xFFFF; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); +}; + +/** + * Returns this Long divided by the given one. + * + * @param {Long} other Long by which to divide. + * @return {Long} this Long divided by the given one. + * @api public + */ +Long.prototype.div = function(other) { + if (other.isZero()) { + throw Error('division by zero'); + } else if (this.isZero()) { + return Long.ZERO; + } + + if (this.equals(Long.MIN_VALUE)) { + if (other.equals(Long.ONE) || + other.equals(Long.NEG_ONE)) { + return Long.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE + } else if (other.equals(Long.MIN_VALUE)) { + return Long.ONE; + } else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shiftRight(1); + var approx = halfThis.div(other).shiftLeft(1); + if (approx.equals(Long.ZERO)) { + return other.isNegative() ? Long.ONE : Long.NEG_ONE; + } else { + var rem = this.subtract(other.multiply(approx)); + var result = approx.add(rem.div(other)); + return result; + } + } + } else if (other.equals(Long.MIN_VALUE)) { + return Long.ZERO; + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().div(other.negate()); + } else { + return this.negate().div(other).negate(); + } + } else if (other.isNegative()) { + return this.div(other.negate()).negate(); + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + var res = Long.ZERO; + var rem = this; + while (rem.greaterThanOrEqual(other)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2); + var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48); + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + var approxRes = Long.fromNumber(approx); + var approxRem = approxRes.multiply(other); + while (approxRem.isNegative() || approxRem.greaterThan(rem)) { + approx -= delta; + approxRes = Long.fromNumber(approx); + approxRem = approxRes.multiply(other); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) { + approxRes = Long.ONE; + } + + res = res.add(approxRes); + rem = rem.subtract(approxRem); + } + return res; +}; + +/** + * Returns this Long modulo the given one. + * + * @param {Long} other Long by which to mod. + * @return {Long} this Long modulo the given one. + * @api public + */ +Long.prototype.modulo = function(other) { + return this.subtract(this.div(other).multiply(other)); +}; + +/** + * The bitwise-NOT of this value. + * + * @return {Long} the bitwise-NOT of this value. + * @api public + */ +Long.prototype.not = function() { + return Long.fromBits(~this.low_, ~this.high_); +}; + +/** + * Returns the bitwise-AND of this Long and the given one. + * + * @param {Long} other the Long with which to AND. + * @return {Long} the bitwise-AND of this and the other. + * @api public + */ +Long.prototype.and = function(other) { + return Long.fromBits(this.low_ & other.low_, this.high_ & other.high_); +}; + +/** + * Returns the bitwise-OR of this Long and the given one. + * + * @param {Long} other the Long with which to OR. + * @return {Long} the bitwise-OR of this and the other. + * @api public + */ +Long.prototype.or = function(other) { + return Long.fromBits(this.low_ | other.low_, this.high_ | other.high_); +}; + +/** + * Returns the bitwise-XOR of this Long and the given one. + * + * @param {Long} other the Long with which to XOR. + * @return {Long} the bitwise-XOR of this and the other. + * @api public + */ +Long.prototype.xor = function(other) { + return Long.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Long} this shifted to the left by the given amount. + * @api public + */ +Long.prototype.shiftLeft = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var low = this.low_; + if (numBits < 32) { + var high = this.high_; + return Long.fromBits( + low << numBits, + (high << numBits) | (low >>> (32 - numBits))); + } else { + return Long.fromBits(0, low << (numBits - 32)); + } + } +}; + +/** + * Returns this Long with bits shifted to the right by the given amount. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Long} this shifted to the right by the given amount. + * @api public + */ +Long.prototype.shiftRight = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return Long.fromBits( + (low >>> numBits) | (high << (32 - numBits)), + high >> numBits); + } else { + return Long.fromBits( + high >> (numBits - 32), + high >= 0 ? 0 : -1); + } + } +}; + +/** + * Returns this Long with bits shifted to the right by the given amount, with the new top bits matching the current sign bit. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Long} this shifted to the right by the given amount, with zeros placed into the new leading bits. + * @api public + */ +Long.prototype.shiftRightUnsigned = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return Long.fromBits( + (low >>> numBits) | (high << (32 - numBits)), + high >>> numBits); + } else if (numBits == 32) { + return Long.fromBits(high, 0); + } else { + return Long.fromBits(high >>> (numBits - 32), 0); + } + } +}; + +/** + * Returns a Long representing the given (32-bit) integer value. + * + * @param {Number} value the 32-bit integer in question. + * @return {Long} the corresponding Long value. + * @api public + */ +Long.fromInt = function(value) { + if (-128 <= value && value < 128) { + var cachedObj = Long.INT_CACHE_[value]; + if (cachedObj) { + return cachedObj; + } + } + + var obj = new Long(value | 0, value < 0 ? -1 : 0); + if (-128 <= value && value < 128) { + Long.INT_CACHE_[value] = obj; + } + return obj; +}; + +/** + * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. + * + * @param {Number} value the number in question. + * @return {Long} the corresponding Long value. + * @api public + */ +Long.fromNumber = function(value) { + if (isNaN(value) || !isFinite(value)) { + return Long.ZERO; + } else if (value <= -Long.TWO_PWR_63_DBL_) { + return Long.MIN_VALUE; + } else if (value + 1 >= Long.TWO_PWR_63_DBL_) { + return Long.MAX_VALUE; + } else if (value < 0) { + return Long.fromNumber(-value).negate(); + } else { + return new Long( + (value % Long.TWO_PWR_32_DBL_) | 0, + (value / Long.TWO_PWR_32_DBL_) | 0); + } +}; + +/** + * Returns a Long representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits. + * + * @param {Number} lowBits the low 32-bits. + * @param {Number} highBits the high 32-bits. + * @return {Long} the corresponding Long value. + * @api public + */ +Long.fromBits = function(lowBits, highBits) { + return new Long(lowBits, highBits); +}; + +/** + * Returns a Long representation of the given string, written using the given radix. + * + * @param {String} str the textual representation of the Long. + * @param {Number} opt_radix the radix in which the text is written. + * @return {Long} the corresponding Long value. + * @api public + */ +Long.fromString = function(str, opt_radix) { + if (str.length == 0) { + throw Error('number format error: empty string'); + } + + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw Error('radix out of range: ' + radix); + } + + if (str.charAt(0) == '-') { + return Long.fromString(str.substring(1), radix).negate(); + } else if (str.indexOf('-') >= 0) { + throw Error('number format error: interior "-" character: ' + str); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = Long.fromNumber(Math.pow(radix, 8)); + + var result = Long.ZERO; + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i); + var value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = Long.fromNumber(Math.pow(radix, size)); + result = result.multiply(power).add(Long.fromNumber(value)); + } else { + result = result.multiply(radixToPower); + result = result.add(Long.fromNumber(value)); + } + } + return result; +}; + +// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the +// from* methods on which they depend. + + +/** + * A cache of the Long representations of small integer values. + * @type {Object} + * @api private + */ +Long.INT_CACHE_ = {}; + +// NOTE: the compiler should inline these constant values below and then remove +// these variables, so there should be no runtime penalty for these. + +/** + * Number used repeated below in calculations. This must appear before the + * first call to any from* function below. + * @type {number} + * @api private + */ +Long.TWO_PWR_16_DBL_ = 1 << 16; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_24_DBL_ = 1 << 24; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_32_DBL_ = Long.TWO_PWR_16_DBL_ * Long.TWO_PWR_16_DBL_; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_31_DBL_ = Long.TWO_PWR_32_DBL_ / 2; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_48_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_16_DBL_; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_64_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_32_DBL_; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_63_DBL_ = Long.TWO_PWR_64_DBL_ / 2; + +/** @type {Long} */ +Long.ZERO = Long.fromInt(0); + +/** @type {Long} */ +Long.ONE = Long.fromInt(1); + +/** @type {Long} */ +Long.NEG_ONE = Long.fromInt(-1); + +/** @type {Long} */ +Long.MAX_VALUE = + Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0); + +/** @type {Long} */ +Long.MIN_VALUE = Long.fromBits(0, 0x80000000 | 0); + +/** + * @type {Long} + * @api private + */ +Long.TWO_PWR_24_ = Long.fromInt(1 << 24); + +/** + * Expose. + */ +if(typeof window === 'undefined') { + exports.Long = Long; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/max_key.js b/node_modules/mongodb/node_modules/bson/lib/bson/max_key.js new file mode 100644 index 0000000..29d558f --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/max_key.js @@ -0,0 +1,15 @@ +/** + * A class representation of the BSON MaxKey type. + * + * @class Represents the BSON MaxKey type. + * @return {MaxKey} + */ +function MaxKey() { + if(!(this instanceof MaxKey)) return new MaxKey(); + + this._bsontype = 'MaxKey'; +} + +if(typeof window === 'undefined') { + exports.MaxKey = MaxKey; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/min_key.js b/node_modules/mongodb/node_modules/bson/lib/bson/min_key.js new file mode 100644 index 0000000..489fbaf --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/min_key.js @@ -0,0 +1,15 @@ +/** + * A class representation of the BSON MinKey type. + * + * @class Represents the BSON MinKey type. + * @return {MinKey} + */ +function MinKey() { + if(!(this instanceof MinKey)) return new MinKey(); + + this._bsontype = 'MinKey'; +} + +if(typeof window === 'undefined') { + exports.MinKey = MinKey; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/objectid.js b/node_modules/mongodb/node_modules/bson/lib/bson/objectid.js new file mode 100644 index 0000000..547e219 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/objectid.js @@ -0,0 +1,251 @@ +/** + * Module dependencies. + */ +if(typeof window === 'undefined') { + var BinaryParser = require('./binary_parser').BinaryParser; +} + +/** + * Machine id. + * + * Create a random 3-byte value (i.e. unique for this + * process). Other drivers use a md5 of the machine id here, but + * that would mean an asyc call to gethostname, so we don't bother. + */ +var MACHINE_ID = parseInt(Math.random() * 0xFFFFFF, 10); + +// Regular expression that checks for hex value +var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$"); + +/** +* Create a new ObjectID instance +* +* @class Represents the BSON ObjectID type +* @param {String|Number} id Can be a 24 byte hex string, 12 byte binary string or a Number. +* @return {Object} instance of ObjectID. +*/ +function ObjectID(id) { + if(!(this instanceof ObjectID)) return new ObjectID(id); + + this._bsontype = 'ObjectID'; + + var self = this; + // Throw an error if it's not a valid setup + if(id != null && 'number' != typeof id && (id.length != 12 && id.length != 24)) throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters in hex format"); + // Generate id based on the input + if(id == null || typeof id == 'number') { + this.id = this.generate(id); + } else if(id != null && id.length === 12) { + this.id = id; + } else if(checkForHexRegExp.test(id)) { + return ObjectID.createFromHexString(id); + } else { + this.id = id; + } + + /** + * Returns the generation time in seconds that this ID was generated. + * + * @field generationTime + * @type {Number} + * @getter + * @setter + * @property return number of seconds in the timestamp part of the 12 byte id. + */ + Object.defineProperty(this, "generationTime", { + enumerable: true + , get: function () { + return Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true)); + } + , set: function (value) { + var value = BinaryParser.encodeInt(value, 32, true, true); + this.id = value + this.id.substr(4); + } + }); +}; + +/** +* Update the ObjectID index used in generating new ObjectID's on the driver +* +* @return {Number} returns next index value. +* @api private +*/ +ObjectID.prototype.get_inc = function() { + return ObjectID.index = (ObjectID.index + 1) % 0xFFFFFF; +}; + +/** +* Update the ObjectID index used in generating new ObjectID's on the driver +* +* @return {Number} returns next index value. +* @api private +*/ +ObjectID.prototype.getInc = function() { + return this.get_inc(); +}; + +/** +* Generate a 12 byte id string used in ObjectID's +* +* @param {Number} [time] optional parameter allowing to pass in a second based timestamp. +* @return {String} return the 12 byte id binary string. +* @api private +*/ +ObjectID.prototype.generate = function(time) { + if ('number' == typeof time) { + var time4Bytes = BinaryParser.encodeInt(time, 32, true, true); + /* for time-based ObjectID the bytes following the time will be zeroed */ + var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false); + var pid2Bytes = BinaryParser.fromShort(typeof process === 'undefined' ? Math.floor(Math.random() * 100000) : process.pid); + var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true); + } else { + var unixTime = parseInt(Date.now()/1000,10); + var time4Bytes = BinaryParser.encodeInt(unixTime, 32, true, true); + var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false); + var pid2Bytes = BinaryParser.fromShort(typeof process === 'undefined' ? Math.floor(Math.random() * 100000) : process.pid); + var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true); + } + + return time4Bytes + machine3Bytes + pid2Bytes + index3Bytes; +}; + +/** +* Return the ObjectID id as a 24 byte hex string representation +* +* @return {String} return the 24 byte hex string representation. +* @api public +*/ +ObjectID.prototype.toHexString = function() { + if(this.__id) return this.__id; + + var hexString = '' + , number + , value; + + for (var index = 0, len = this.id.length; index < len; index++) { + value = BinaryParser.toByte(this.id[index]); + number = value <= 15 + ? '0' + value.toString(16) + : value.toString(16); + hexString = hexString + number; + } + + return this.__id = hexString; +}; + +/** +* Converts the id into a 24 byte hex string for printing +* +* @return {String} return the 24 byte hex string representation. +* @api private +*/ +ObjectID.prototype.toString = function() { + return this.toHexString(); +}; + +/** +* Converts to a string representation of this Id. +* +* @return {String} return the 24 byte hex string representation. +* @api private +*/ +ObjectID.prototype.inspect = ObjectID.prototype.toString; + +/** +* Converts to its JSON representation. +* +* @return {String} return the 24 byte hex string representation. +* @api private +*/ +ObjectID.prototype.toJSON = function() { + return this.toHexString(); +}; + +/** +* Compares the equality of this ObjectID with `otherID`. +* +* @param {Object} otherID ObjectID instance to compare against. +* @return {Bool} the result of comparing two ObjectID's +* @api public +*/ +ObjectID.prototype.equals = function equals (otherID) { + var id = (otherID instanceof ObjectID || otherID.toHexString) + ? otherID.id + : ObjectID.createFromHexString(otherID).id; + + return this.id === id; +} + +/** +* Returns the generation time in seconds that this ID was generated. +* +* @return {Number} return number of seconds in the timestamp part of the 12 byte id. +* @api public +*/ +ObjectID.prototype.getTimestamp = function() { + var timestamp = new Date(); + timestamp.setTime(Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true)) * 1000); + return timestamp; +} + +/** +* @ignore +* @api private +*/ +ObjectID.index = 0; + +ObjectID.createPk = function createPk () { + return new ObjectID(); +}; + +/** +* Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID. +* +* @param {Number} time an integer number representing a number of seconds. +* @return {ObjectID} return the created ObjectID +* @api public +*/ +ObjectID.createFromTime = function createFromHexString(time) { + var time4Bytes = BinaryParser.encodeInt(time, 32, true, true); + var objectID = new ObjectID(); + objectID.id = BinaryParser.encodeInt(time, 32, true, true) + BinaryParser.encodeInt(0, 64, true, true) + return objectID; +}; + +/** +* Creates an ObjectID from a hex string representation of an ObjectID. +* +* @param {String} hexString create a ObjectID from a passed in 24 byte hexstring. +* @return {ObjectID} return the created ObjectID +* @api public +*/ +ObjectID.createFromHexString = function createFromHexString (hexString) { + // Throw an error if it's not a valid setup + if(typeof hexString === 'undefined' || hexString != null && hexString.length != 24) throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters in hex format"); + + var len = hexString.length; + + if(len > 12*2) { + throw new Error('Id cannot be longer than 12 bytes'); + } + + var result = '' + , string + , number; + + for (var index = 0; index < len; index += 2) { + string = hexString.substr(index, 2); + number = parseInt(string, 16); + result += BinaryParser.fromByte(number); + } + + return new ObjectID(result); +}; + +/** + * Expose. + */ +if(typeof window === 'undefined') { + exports.ObjectID = ObjectID; +} + diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/symbol.js b/node_modules/mongodb/node_modules/bson/lib/bson/symbol.js new file mode 100644 index 0000000..e88d083 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/symbol.js @@ -0,0 +1,50 @@ +/** + * A class representation of the BSON Symbol type. + * + * @class Represents the BSON Symbol type. + * @param {String} value the string representing the symbol. + * @return {Symbol} + */ +function Symbol(value) { + if(!(this instanceof Symbol)) return new Symbol(value); + this._bsontype = 'Symbol'; + this.value = value; +} + +/** + * Access the wrapped string value. + * + * @return {String} returns the wrapped string. + * @api public + */ +Symbol.prototype.valueOf = function() { + return this.value; +}; + +/** + * @ignore + * @api private + */ +Symbol.prototype.toString = function() { + return this.value; +} + +/** + * @ignore + * @api private + */ +Symbol.prototype.inspect = function() { + return this.value; +} + +/** + * @ignore + * @api private + */ +Symbol.prototype.toJSON = function() { + return this.value; +} + +if(typeof window === 'undefined') { + exports.Symbol = Symbol; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/lib/bson/timestamp.js b/node_modules/mongodb/node_modules/bson/lib/bson/timestamp.js new file mode 100644 index 0000000..d570074 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/lib/bson/timestamp.js @@ -0,0 +1,855 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright 2009 Google Inc. All Rights Reserved + +/** + * Defines a Timestamp class for representing a 64-bit two's-complement + * integer value, which faithfully simulates the behavior of a Java "Timestamp". This + * implementation is derived from TimestampLib in GWT. + * + * Constructs a 64-bit two's-complement integer, given its low and high 32-bit + * values as *signed* integers. See the from* functions below for more + * convenient ways of constructing Timestamps. + * + * The internal representation of a Timestamp is the two given signed, 32-bit values. + * We use 32-bit pieces because these are the size of integers on which + * Javascript performs bit-operations. For operations like addition and + * multiplication, we split each number into 16-bit pieces, which can easily be + * multiplied within Javascript's floating-point representation without overflow + * or change in sign. + * + * In the algorithms below, we frequently reduce the negative case to the + * positive case by negating the input(s) and then post-processing the result. + * Note that we must ALWAYS check specially whether those values are MIN_VALUE + * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as + * a positive number, it overflows back into a negative). Not handling this + * case would often result in infinite recursion. + * + * @class Represents the BSON Timestamp type. + * @param {Number} low the low (signed) 32 bits of the Timestamp. + * @param {Number} high the high (signed) 32 bits of the Timestamp. + */ +function Timestamp(low, high) { + if(!(this instanceof Timestamp)) return new Timestamp(low, high); + this._bsontype = 'Timestamp'; + /** + * @type {number} + * @api private + */ + this.low_ = low | 0; // force into 32 signed bits. + + /** + * @type {number} + * @api private + */ + this.high_ = high | 0; // force into 32 signed bits. +}; + +/** + * Return the int value. + * + * @return {Number} the value, assuming it is a 32-bit integer. + * @api public + */ +Timestamp.prototype.toInt = function() { + return this.low_; +}; + +/** + * Return the Number value. + * + * @return {Number} the closest floating-point representation to this value. + * @api public + */ +Timestamp.prototype.toNumber = function() { + return this.high_ * Timestamp.TWO_PWR_32_DBL_ + + this.getLowBitsUnsigned(); +}; + +/** + * Return the JSON value. + * + * @return {String} the JSON representation. + * @api public + */ +Timestamp.prototype.toJSON = function() { + return this.toString(); +} + +/** + * Return the String value. + * + * @param {Number} [opt_radix] the radix in which the text should be written. + * @return {String} the textual representation of this value. + * @api public + */ +Timestamp.prototype.toString = function(opt_radix) { + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw Error('radix out of range: ' + radix); + } + + if (this.isZero()) { + return '0'; + } + + if (this.isNegative()) { + if (this.equals(Timestamp.MIN_VALUE)) { + // We need to change the Timestamp value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixTimestamp = Timestamp.fromNumber(radix); + var div = this.div(radixTimestamp); + var rem = div.multiply(radixTimestamp).subtract(this); + return div.toString(radix) + rem.toInt().toString(radix); + } else { + return '-' + this.negate().toString(radix); + } + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = Timestamp.fromNumber(Math.pow(radix, 6)); + + var rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower); + var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt(); + var digits = intval.toString(radix); + + rem = remDiv; + if (rem.isZero()) { + return digits + result; + } else { + while (digits.length < 6) { + digits = '0' + digits; + } + result = '' + digits + result; + } + } +}; + +/** + * Return the high 32-bits value. + * + * @return {Number} the high 32-bits as a signed value. + * @api public + */ +Timestamp.prototype.getHighBits = function() { + return this.high_; +}; + +/** + * Return the low 32-bits value. + * + * @return {Number} the low 32-bits as a signed value. + * @api public + */ +Timestamp.prototype.getLowBits = function() { + return this.low_; +}; + +/** + * Return the low unsigned 32-bits value. + * + * @return {Number} the low 32-bits as an unsigned value. + * @api public + */ +Timestamp.prototype.getLowBitsUnsigned = function() { + return (this.low_ >= 0) ? + this.low_ : Timestamp.TWO_PWR_32_DBL_ + this.low_; +}; + +/** + * Returns the number of bits needed to represent the absolute value of this Timestamp. + * + * @return {Number} Returns the number of bits needed to represent the absolute value of this Timestamp. + * @api public + */ +Timestamp.prototype.getNumBitsAbs = function() { + if (this.isNegative()) { + if (this.equals(Timestamp.MIN_VALUE)) { + return 64; + } else { + return this.negate().getNumBitsAbs(); + } + } else { + var val = this.high_ != 0 ? this.high_ : this.low_; + for (var bit = 31; bit > 0; bit--) { + if ((val & (1 << bit)) != 0) { + break; + } + } + return this.high_ != 0 ? bit + 33 : bit + 1; + } +}; + +/** + * Return whether this value is zero. + * + * @return {Boolean} whether this value is zero. + * @api public + */ +Timestamp.prototype.isZero = function() { + return this.high_ == 0 && this.low_ == 0; +}; + +/** + * Return whether this value is negative. + * + * @return {Boolean} whether this value is negative. + * @api public + */ +Timestamp.prototype.isNegative = function() { + return this.high_ < 0; +}; + +/** + * Return whether this value is odd. + * + * @return {Boolean} whether this value is odd. + * @api public + */ +Timestamp.prototype.isOdd = function() { + return (this.low_ & 1) == 1; +}; + +/** + * Return whether this Timestamp equals the other + * + * @param {Timestamp} other Timestamp to compare against. + * @return {Boolean} whether this Timestamp equals the other + * @api public + */ +Timestamp.prototype.equals = function(other) { + return (this.high_ == other.high_) && (this.low_ == other.low_); +}; + +/** + * Return whether this Timestamp does not equal the other. + * + * @param {Timestamp} other Timestamp to compare against. + * @return {Boolean} whether this Timestamp does not equal the other. + * @api public + */ +Timestamp.prototype.notEquals = function(other) { + return (this.high_ != other.high_) || (this.low_ != other.low_); +}; + +/** + * Return whether this Timestamp is less than the other. + * + * @param {Timestamp} other Timestamp to compare against. + * @return {Boolean} whether this Timestamp is less than the other. + * @api public + */ +Timestamp.prototype.lessThan = function(other) { + return this.compare(other) < 0; +}; + +/** + * Return whether this Timestamp is less than or equal to the other. + * + * @param {Timestamp} other Timestamp to compare against. + * @return {Boolean} whether this Timestamp is less than or equal to the other. + * @api public + */ +Timestamp.prototype.lessThanOrEqual = function(other) { + return this.compare(other) <= 0; +}; + +/** + * Return whether this Timestamp is greater than the other. + * + * @param {Timestamp} other Timestamp to compare against. + * @return {Boolean} whether this Timestamp is greater than the other. + * @api public + */ +Timestamp.prototype.greaterThan = function(other) { + return this.compare(other) > 0; +}; + +/** + * Return whether this Timestamp is greater than or equal to the other. + * + * @param {Timestamp} other Timestamp to compare against. + * @return {Boolean} whether this Timestamp is greater than or equal to the other. + * @api public + */ +Timestamp.prototype.greaterThanOrEqual = function(other) { + return this.compare(other) >= 0; +}; + +/** + * Compares this Timestamp with the given one. + * + * @param {Timestamp} other Timestamp to compare against. + * @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater. + * @api public + */ +Timestamp.prototype.compare = function(other) { + if (this.equals(other)) { + return 0; + } + + var thisNeg = this.isNegative(); + var otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) { + return -1; + } + if (!thisNeg && otherNeg) { + return 1; + } + + // at this point, the signs are the same, so subtraction will not overflow + if (this.subtract(other).isNegative()) { + return -1; + } else { + return 1; + } +}; + +/** + * The negation of this value. + * + * @return {Timestamp} the negation of this value. + * @api public + */ +Timestamp.prototype.negate = function() { + if (this.equals(Timestamp.MIN_VALUE)) { + return Timestamp.MIN_VALUE; + } else { + return this.not().add(Timestamp.ONE); + } +}; + +/** + * Returns the sum of this and the given Timestamp. + * + * @param {Timestamp} other Timestamp to add to this one. + * @return {Timestamp} the sum of this and the given Timestamp. + * @api public + */ +Timestamp.prototype.add = function(other) { + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xFFFF; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xFFFF; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xFFFF; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + return Timestamp.fromBits((c16 << 16) | c00, (c48 << 16) | c32); +}; + +/** + * Returns the difference of this and the given Timestamp. + * + * @param {Timestamp} other Timestamp to subtract from this. + * @return {Timestamp} the difference of this and the given Timestamp. + * @api public + */ +Timestamp.prototype.subtract = function(other) { + return this.add(other.negate()); +}; + +/** + * Returns the product of this and the given Timestamp. + * + * @param {Timestamp} other Timestamp to multiply with this. + * @return {Timestamp} the product of this and the other. + * @api public + */ +Timestamp.prototype.multiply = function(other) { + if (this.isZero()) { + return Timestamp.ZERO; + } else if (other.isZero()) { + return Timestamp.ZERO; + } + + if (this.equals(Timestamp.MIN_VALUE)) { + return other.isOdd() ? Timestamp.MIN_VALUE : Timestamp.ZERO; + } else if (other.equals(Timestamp.MIN_VALUE)) { + return this.isOdd() ? Timestamp.MIN_VALUE : Timestamp.ZERO; + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().multiply(other.negate()); + } else { + return this.negate().multiply(other).negate(); + } + } else if (other.isNegative()) { + return this.multiply(other.negate()).negate(); + } + + // If both Timestamps are small, use float multiplication + if (this.lessThan(Timestamp.TWO_PWR_24_) && + other.lessThan(Timestamp.TWO_PWR_24_)) { + return Timestamp.fromNumber(this.toNumber() * other.toNumber()); + } + + // Divide each Timestamp into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xFFFF; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xFFFF; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xFFFF; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + return Timestamp.fromBits((c16 << 16) | c00, (c48 << 16) | c32); +}; + +/** + * Returns this Timestamp divided by the given one. + * + * @param {Timestamp} other Timestamp by which to divide. + * @return {Timestamp} this Timestamp divided by the given one. + * @api public + */ +Timestamp.prototype.div = function(other) { + if (other.isZero()) { + throw Error('division by zero'); + } else if (this.isZero()) { + return Timestamp.ZERO; + } + + if (this.equals(Timestamp.MIN_VALUE)) { + if (other.equals(Timestamp.ONE) || + other.equals(Timestamp.NEG_ONE)) { + return Timestamp.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE + } else if (other.equals(Timestamp.MIN_VALUE)) { + return Timestamp.ONE; + } else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shiftRight(1); + var approx = halfThis.div(other).shiftLeft(1); + if (approx.equals(Timestamp.ZERO)) { + return other.isNegative() ? Timestamp.ONE : Timestamp.NEG_ONE; + } else { + var rem = this.subtract(other.multiply(approx)); + var result = approx.add(rem.div(other)); + return result; + } + } + } else if (other.equals(Timestamp.MIN_VALUE)) { + return Timestamp.ZERO; + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().div(other.negate()); + } else { + return this.negate().div(other).negate(); + } + } else if (other.isNegative()) { + return this.div(other.negate()).negate(); + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + var res = Timestamp.ZERO; + var rem = this; + while (rem.greaterThanOrEqual(other)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2); + var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48); + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + var approxRes = Timestamp.fromNumber(approx); + var approxRem = approxRes.multiply(other); + while (approxRem.isNegative() || approxRem.greaterThan(rem)) { + approx -= delta; + approxRes = Timestamp.fromNumber(approx); + approxRem = approxRes.multiply(other); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) { + approxRes = Timestamp.ONE; + } + + res = res.add(approxRes); + rem = rem.subtract(approxRem); + } + return res; +}; + +/** + * Returns this Timestamp modulo the given one. + * + * @param {Timestamp} other Timestamp by which to mod. + * @return {Timestamp} this Timestamp modulo the given one. + * @api public + */ +Timestamp.prototype.modulo = function(other) { + return this.subtract(this.div(other).multiply(other)); +}; + +/** + * The bitwise-NOT of this value. + * + * @return {Timestamp} the bitwise-NOT of this value. + * @api public + */ +Timestamp.prototype.not = function() { + return Timestamp.fromBits(~this.low_, ~this.high_); +}; + +/** + * Returns the bitwise-AND of this Timestamp and the given one. + * + * @param {Timestamp} other the Timestamp with which to AND. + * @return {Timestamp} the bitwise-AND of this and the other. + * @api public + */ +Timestamp.prototype.and = function(other) { + return Timestamp.fromBits(this.low_ & other.low_, this.high_ & other.high_); +}; + +/** + * Returns the bitwise-OR of this Timestamp and the given one. + * + * @param {Timestamp} other the Timestamp with which to OR. + * @return {Timestamp} the bitwise-OR of this and the other. + * @api public + */ +Timestamp.prototype.or = function(other) { + return Timestamp.fromBits(this.low_ | other.low_, this.high_ | other.high_); +}; + +/** + * Returns the bitwise-XOR of this Timestamp and the given one. + * + * @param {Timestamp} other the Timestamp with which to XOR. + * @return {Timestamp} the bitwise-XOR of this and the other. + * @api public + */ +Timestamp.prototype.xor = function(other) { + return Timestamp.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_); +}; + +/** + * Returns this Timestamp with bits shifted to the left by the given amount. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Timestamp} this shifted to the left by the given amount. + * @api public + */ +Timestamp.prototype.shiftLeft = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var low = this.low_; + if (numBits < 32) { + var high = this.high_; + return Timestamp.fromBits( + low << numBits, + (high << numBits) | (low >>> (32 - numBits))); + } else { + return Timestamp.fromBits(0, low << (numBits - 32)); + } + } +}; + +/** + * Returns this Timestamp with bits shifted to the right by the given amount. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Timestamp} this shifted to the right by the given amount. + * @api public + */ +Timestamp.prototype.shiftRight = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return Timestamp.fromBits( + (low >>> numBits) | (high << (32 - numBits)), + high >> numBits); + } else { + return Timestamp.fromBits( + high >> (numBits - 32), + high >= 0 ? 0 : -1); + } + } +}; + +/** + * Returns this Timestamp with bits shifted to the right by the given amount, with the new top bits matching the current sign bit. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Timestamp} this shifted to the right by the given amount, with zeros placed into the new leading bits. + * @api public + */ +Timestamp.prototype.shiftRightUnsigned = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return Timestamp.fromBits( + (low >>> numBits) | (high << (32 - numBits)), + high >>> numBits); + } else if (numBits == 32) { + return Timestamp.fromBits(high, 0); + } else { + return Timestamp.fromBits(high >>> (numBits - 32), 0); + } + } +}; + +/** + * Returns a Timestamp representing the given (32-bit) integer value. + * + * @param {Number} value the 32-bit integer in question. + * @return {Timestamp} the corresponding Timestamp value. + * @api public + */ +Timestamp.fromInt = function(value) { + if (-128 <= value && value < 128) { + var cachedObj = Timestamp.INT_CACHE_[value]; + if (cachedObj) { + return cachedObj; + } + } + + var obj = new Timestamp(value | 0, value < 0 ? -1 : 0); + if (-128 <= value && value < 128) { + Timestamp.INT_CACHE_[value] = obj; + } + return obj; +}; + +/** + * Returns a Timestamp representing the given value, provided that it is a finite number. Otherwise, zero is returned. + * + * @param {Number} value the number in question. + * @return {Timestamp} the corresponding Timestamp value. + * @api public + */ +Timestamp.fromNumber = function(value) { + if (isNaN(value) || !isFinite(value)) { + return Timestamp.ZERO; + } else if (value <= -Timestamp.TWO_PWR_63_DBL_) { + return Timestamp.MIN_VALUE; + } else if (value + 1 >= Timestamp.TWO_PWR_63_DBL_) { + return Timestamp.MAX_VALUE; + } else if (value < 0) { + return Timestamp.fromNumber(-value).negate(); + } else { + return new Timestamp( + (value % Timestamp.TWO_PWR_32_DBL_) | 0, + (value / Timestamp.TWO_PWR_32_DBL_) | 0); + } +}; + +/** + * Returns a Timestamp representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits. + * + * @param {Number} lowBits the low 32-bits. + * @param {Number} highBits the high 32-bits. + * @return {Timestamp} the corresponding Timestamp value. + * @api public + */ +Timestamp.fromBits = function(lowBits, highBits) { + return new Timestamp(lowBits, highBits); +}; + +/** + * Returns a Timestamp representation of the given string, written using the given radix. + * + * @param {String} str the textual representation of the Timestamp. + * @param {Number} opt_radix the radix in which the text is written. + * @return {Timestamp} the corresponding Timestamp value. + * @api public + */ +Timestamp.fromString = function(str, opt_radix) { + if (str.length == 0) { + throw Error('number format error: empty string'); + } + + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw Error('radix out of range: ' + radix); + } + + if (str.charAt(0) == '-') { + return Timestamp.fromString(str.substring(1), radix).negate(); + } else if (str.indexOf('-') >= 0) { + throw Error('number format error: interior "-" character: ' + str); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = Timestamp.fromNumber(Math.pow(radix, 8)); + + var result = Timestamp.ZERO; + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i); + var value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = Timestamp.fromNumber(Math.pow(radix, size)); + result = result.multiply(power).add(Timestamp.fromNumber(value)); + } else { + result = result.multiply(radixToPower); + result = result.add(Timestamp.fromNumber(value)); + } + } + return result; +}; + +// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the +// from* methods on which they depend. + + +/** + * A cache of the Timestamp representations of small integer values. + * @type {Object} + * @api private + */ +Timestamp.INT_CACHE_ = {}; + +// NOTE: the compiler should inline these constant values below and then remove +// these variables, so there should be no runtime penalty for these. + +/** + * Number used repeated below in calculations. This must appear before the + * first call to any from* function below. + * @type {number} + * @api private + */ +Timestamp.TWO_PWR_16_DBL_ = 1 << 16; + +/** + * @type {number} + * @api private + */ +Timestamp.TWO_PWR_24_DBL_ = 1 << 24; + +/** + * @type {number} + * @api private + */ +Timestamp.TWO_PWR_32_DBL_ = Timestamp.TWO_PWR_16_DBL_ * Timestamp.TWO_PWR_16_DBL_; + +/** + * @type {number} + * @api private + */ +Timestamp.TWO_PWR_31_DBL_ = Timestamp.TWO_PWR_32_DBL_ / 2; + +/** + * @type {number} + * @api private + */ +Timestamp.TWO_PWR_48_DBL_ = Timestamp.TWO_PWR_32_DBL_ * Timestamp.TWO_PWR_16_DBL_; + +/** + * @type {number} + * @api private + */ +Timestamp.TWO_PWR_64_DBL_ = Timestamp.TWO_PWR_32_DBL_ * Timestamp.TWO_PWR_32_DBL_; + +/** + * @type {number} + * @api private + */ +Timestamp.TWO_PWR_63_DBL_ = Timestamp.TWO_PWR_64_DBL_ / 2; + +/** @type {Timestamp} */ +Timestamp.ZERO = Timestamp.fromInt(0); + +/** @type {Timestamp} */ +Timestamp.ONE = Timestamp.fromInt(1); + +/** @type {Timestamp} */ +Timestamp.NEG_ONE = Timestamp.fromInt(-1); + +/** @type {Timestamp} */ +Timestamp.MAX_VALUE = + Timestamp.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0); + +/** @type {Timestamp} */ +Timestamp.MIN_VALUE = Timestamp.fromBits(0, 0x80000000 | 0); + +/** + * @type {Timestamp} + * @api private + */ +Timestamp.TWO_PWR_24_ = Timestamp.fromInt(1 << 24); + +/** + * Expose. + */ +if(typeof window === 'undefined') { + exports.Timestamp = Timestamp; +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/package.json b/node_modules/mongodb/node_modules/bson/package.json new file mode 100755 index 0000000..18f539d --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/package.json @@ -0,0 +1,58 @@ +{ + "name": "bson", + "description": "A bson parser for node.js and the browser", + "keywords": [ + "mongodb", + "bson", + "parser" + ], + "version": "0.0.4", + "author": { + "name": "Christian Amor Kvalheim", + "email": "christkv@gmail.com" + }, + "contributors": [], + "repository": { + "type": "git", + "url": "git@github.com:christkv/bson.git" + }, + "bugs": { + "email": "node-mongodb-native@googlegroups.com", + "url": "https://github.com/christkv/bson/issues" + }, + "devDependencies": { + "nodeunit": "0.7.3", + "gleak": "0.2.3" + }, + "config": { + "native": false + }, + "main": "./lib/bson/index", + "directories": { + "lib": "./lib/bson" + }, + "engines": { + "node": ">=0.4.12" + }, + "scripts": { + "install": "node install.js", + "test": "make test" + }, + "licenses": [ + { + "type": "Apache License, Version 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + } + ], + "_id": "bson@0.0.4", + "dependencies": {}, + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "e05fa2e643436084acc8ad960ada487d8988ddd5" + }, + "_from": "bson@0.0.4" +} diff --git a/node_modules/mongodb/node_modules/bson/test/browser/bson_test.js b/node_modules/mongodb/node_modules/bson/test/browser/bson_test.js new file mode 100644 index 0000000..c7d9a67 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/browser/bson_test.js @@ -0,0 +1,242 @@ +this.bson_test = { + 'Full document serialization and deserialization': function (test) { + var motherOfAllDocuments = { + 'string': "客家话", + 'array': [1,2,3], + 'hash': {'a':1, 'b':2}, + 'date': new Date(), + 'oid': new ObjectID(), + 'binary': new Binary('hello world'), + 'int': 42, + 'float': 33.3333, + 'regexp': /regexp/, + 'boolean': true, + 'long': Long.fromNumber(100), + 'where': new Code('this.a > i', {i:1}), + 'dbref': new DBRef('namespace', new ObjectID(), 'integration_tests_'), + 'minkey': new MinKey(), + 'maxkey': new MaxKey() + } + + // Let's serialize it + var data = BSON.serialize(motherOfAllDocuments, true, true, false); + // Deserialize the object + var object = BSON.deserialize(data); + + // Asserts + test.equal(Utf8.decode(motherOfAllDocuments.string), object.string); + test.deepEqual(motherOfAllDocuments.array, object.array); + test.deepEqual(motherOfAllDocuments.date, object.date); + test.deepEqual(motherOfAllDocuments.oid.toHexString(), object.oid.toHexString()); + test.deepEqual(motherOfAllDocuments.binary.length(), object.binary.length()); + test.ok(assertArrayEqual(motherOfAllDocuments.binary.value(true), object.binary.value(true))); + test.deepEqual(motherOfAllDocuments.int, object.int); + test.deepEqual(motherOfAllDocuments.float, object.float); + test.deepEqual(motherOfAllDocuments.regexp, object.regexp); + test.deepEqual(motherOfAllDocuments.boolean, object.boolean); + test.deepEqual(motherOfAllDocuments.long.toNumber(), object.long); + test.deepEqual(motherOfAllDocuments.where, object.where); + test.deepEqual(motherOfAllDocuments.dbref.oid.toHexString(), object.dbref.oid.toHexString()); + test.deepEqual(motherOfAllDocuments.dbref.namespace, object.dbref.namespace); + test.deepEqual(motherOfAllDocuments.dbref.db, object.dbref.db); + test.deepEqual(motherOfAllDocuments.minkey, object.minkey); + test.deepEqual(motherOfAllDocuments.maxkey, object.maxkey); + test.done(); + }, + + 'exercise all the binary object constructor methods': function (test) { + // Construct using array + var string = 'hello world'; + // String to array + var array = stringToArrayBuffer(string); + + // Binary from array buffer + var binary = new Binary(stringToArrayBuffer(string)); + test.ok(string.length, binary.buffer.length); + test.ok(assertArrayEqual(array, binary.buffer)); + + // Construct using number of chars + binary = new Binary(5); + test.ok(5, binary.buffer.length); + + // Construct using an Array + var binary = new Binary(stringToArray(string)); + test.ok(string.length, binary.buffer.length); + test.ok(assertArrayEqual(array, binary.buffer)); + + // Construct using a string + var binary = new Binary(string); + test.ok(string.length, binary.buffer.length); + test.ok(assertArrayEqual(array, binary.buffer)); + test.done(); + }, + + 'exercise the put binary object method for an instance when using Uint8Array': function (test) { + // Construct using array + var string = 'hello world'; + // String to array + var array = stringToArrayBuffer(string + 'a'); + + // Binary from array buffer + var binary = new Binary(stringToArrayBuffer(string)); + test.ok(string.length, binary.buffer.length); + + // Write a byte to the array + binary.put('a') + + // Verify that the data was writtencorrectly + test.equal(string.length + 1, binary.position); + test.ok(assertArrayEqual(array, binary.value(true))); + test.equal('hello worlda', binary.value()); + + // Exercise a binary with lots of space in the buffer + var binary = new Binary(); + test.ok(Binary.BUFFER_SIZE, binary.buffer.length); + + // Write a byte to the array + binary.put('a') + + // Verify that the data was writtencorrectly + test.equal(1, binary.position); + test.ok(assertArrayEqual(['a'.charCodeAt(0)], binary.value(true))); + test.equal('a', binary.value()); + test.done(); + }, + + 'exercise the write binary object method for an instance when using Uint8Array': function (test) { + // Construct using array + var string = 'hello world'; + // Array + var writeArrayBuffer = new Uint8Array(new ArrayBuffer(1)); + writeArrayBuffer[0] = 'a'.charCodeAt(0); + var arrayBuffer = ['a'.charCodeAt(0)]; + + // Binary from array buffer + var binary = new Binary(stringToArrayBuffer(string)); + test.ok(string.length, binary.buffer.length); + + // Write a string starting at end of buffer + binary.write('a'); + test.equal('hello worlda', binary.value()); + // Write a string starting at index 0 + binary.write('a', 0); + test.equal('aello worlda', binary.value()); + // Write a arraybuffer starting at end of buffer + binary.write(writeArrayBuffer); + test.equal('aello worldaa', binary.value()); + // Write a arraybuffer starting at position 5 + binary.write(writeArrayBuffer, 5); + test.equal('aelloaworldaa', binary.value()); + // Write a array starting at end of buffer + binary.write(arrayBuffer); + test.equal('aelloaworldaaa', binary.value()); + // Write a array starting at position 6 + binary.write(arrayBuffer, 6); + test.equal('aelloaaorldaaa', binary.value()); + test.done(); + }, + + 'exercise the read binary object method for an instance when using Uint8Array': function (test) { + // Construct using array + var string = 'hello world'; + var array = stringToArrayBuffer(string); + + // Binary from array buffer + var binary = new Binary(stringToArrayBuffer(string)); + test.ok(string.length, binary.buffer.length); + + // Read the first 2 bytes + var data = binary.read(0, 2); + test.ok(assertArrayEqual(stringToArrayBuffer('he'), data)); + + // Read the entire field + var data = binary.read(0); + test.ok(assertArrayEqual(stringToArrayBuffer(string), data)); + + // Read 3 bytes + var data = binary.read(6, 5); + test.ok(assertArrayEqual(stringToArrayBuffer('world'), data)); + test.done(); + } +}; + +var assertArrayEqual = function(array1, array2) { + if(array1.length != array2.length) return false; + for(var i = 0; i < array1.length; i++) { + if(array1[i] != array2[i]) return false; + } + + return true; +} + +// String to arraybuffer +var stringToArrayBuffer = function(string) { + var dataBuffer = new Uint8Array(new ArrayBuffer(string.length)); + // Return the strings + for(var i = 0; i < string.length; i++) { + dataBuffer[i] = string.charCodeAt(i); + } + // Return the data buffer + return dataBuffer; +} + +// String to arraybuffer +var stringToArray = function(string) { + var dataBuffer = new Array(string.length); + // Return the strings + for(var i = 0; i < string.length; i++) { + dataBuffer[i] = string.charCodeAt(i); + } + // Return the data buffer + return dataBuffer; +} + +var Utf8 = { + // public method for url encoding + encode : function (string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + + return utftext; + }, + + // public method for url decoding + decode : function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while ( i < utftext.length ) { + c = utftext.charCodeAt(i); + if(c < 128) { + string += String.fromCharCode(c); + i++; + } else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; + } +} diff --git a/node_modules/mongodb/node_modules/bson/test/browser/nodeunit.js b/node_modules/mongodb/node_modules/bson/test/browser/nodeunit.js new file mode 100644 index 0000000..af7fd0b --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/browser/nodeunit.js @@ -0,0 +1,2034 @@ +/*! + * Nodeunit + * https://github.com/caolan/nodeunit + * Copyright (c) 2010 Caolan McMahon + * MIT Licensed + * + * json2.js + * http://www.JSON.org/json2.js + * Public Domain. + * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + */ +nodeunit = (function(){ +/* + http://www.JSON.org/json2.js + 2010-11-17 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, strict: false, regexp: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON = {}; + +(function () { + "use strict"; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + 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 && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(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) { + k = rep[i]; + if (typeof k === 'string') { + 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.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. + + if (typeof JSON.stringify !== 'function') { + 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. + + if (typeof JSON.parse !== 'function') { + 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.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'); + }; + } +}()); +var assert = this.assert = {}; +var types = {}; +var core = {}; +var nodeunit = {}; +var reporter = {}; +/*global setTimeout: false, console: false */ +(function () { + + var async = {}; + + // global on the server, window in the browser + var root = this, + previous_async = root.async; + + if (typeof module !== 'undefined' && module.exports) { + module.exports = async; + } + else { + root.async = async; + } + + async.noConflict = function () { + root.async = previous_async; + return async; + }; + + //// cross-browser compatiblity functions //// + + var _forEach = function (arr, iterator) { + if (arr.forEach) { + return arr.forEach(iterator); + } + for (var i = 0; i < arr.length; i += 1) { + iterator(arr[i], i, arr); + } + }; + + var _map = function (arr, iterator) { + if (arr.map) { + return arr.map(iterator); + } + var results = []; + _forEach(arr, function (x, i, a) { + results.push(iterator(x, i, a)); + }); + return results; + }; + + var _reduce = function (arr, iterator, memo) { + if (arr.reduce) { + return arr.reduce(iterator, memo); + } + _forEach(arr, function (x, i, a) { + memo = iterator(memo, x, i, a); + }); + return memo; + }; + + var _keys = function (obj) { + if (Object.keys) { + return Object.keys(obj); + } + var keys = []; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + keys.push(k); + } + } + return keys; + }; + + var _indexOf = function (arr, item) { + if (arr.indexOf) { + return arr.indexOf(item); + } + for (var i = 0; i < arr.length; i += 1) { + if (arr[i] === item) { + return i; + } + } + return -1; + }; + + //// exported async module functions //// + + //// nextTick implementation with browser-compatible fallback //// + if (typeof process === 'undefined' || !(process.nextTick)) { + async.nextTick = function (fn) { + setTimeout(fn, 0); + }; + } + else { + async.nextTick = process.nextTick; + } + + async.forEach = function (arr, iterator, callback) { + if (!arr.length) { + return callback(); + } + var completed = 0; + _forEach(arr, function (x) { + iterator(x, function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed === arr.length) { + callback(); + } + } + }); + }); + }; + + async.forEachSeries = function (arr, iterator, callback) { + if (!arr.length) { + return callback(); + } + var completed = 0; + var iterate = function () { + iterator(arr[completed], function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed === arr.length) { + callback(); + } + else { + iterate(); + } + } + }); + }; + iterate(); + }; + + + var doParallel = function (fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [async.forEach].concat(args)); + }; + }; + var doSeries = function (fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [async.forEachSeries].concat(args)); + }; + }; + + + var _asyncMap = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (err, v) { + results[x.index] = v; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + }; + async.map = doParallel(_asyncMap); + async.mapSeries = doSeries(_asyncMap); + + + // reduce only has a series version, as doing reduce in parallel won't + // work in many situations. + async.reduce = function (arr, memo, iterator, callback) { + async.forEachSeries(arr, function (x, callback) { + iterator(memo, x, function (err, v) { + memo = v; + callback(err); + }); + }, function (err) { + callback(err, memo); + }); + }; + // inject alias + async.inject = async.reduce; + // foldl alias + async.foldl = async.reduce; + + async.reduceRight = function (arr, memo, iterator, callback) { + var reversed = _map(arr, function (x) { + return x; + }).reverse(); + async.reduce(reversed, memo, iterator, callback); + }; + // foldr alias + async.foldr = async.reduceRight; + + var _filter = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (v) { + if (v) { + results.push(x); + } + callback(); + }); + }, function (err) { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + }; + async.filter = doParallel(_filter); + async.filterSeries = doSeries(_filter); + // select alias + async.select = async.filter; + async.selectSeries = async.filterSeries; + + var _reject = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (v) { + if (!v) { + results.push(x); + } + callback(); + }); + }, function (err) { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + }; + async.reject = doParallel(_reject); + async.rejectSeries = doSeries(_reject); + + var _detect = function (eachfn, arr, iterator, main_callback) { + eachfn(arr, function (x, callback) { + iterator(x, function (result) { + if (result) { + main_callback(x); + } + else { + callback(); + } + }); + }, function (err) { + main_callback(); + }); + }; + async.detect = doParallel(_detect); + async.detectSeries = doSeries(_detect); + + async.some = function (arr, iterator, main_callback) { + async.forEach(arr, function (x, callback) { + iterator(x, function (v) { + if (v) { + main_callback(true); + main_callback = function () {}; + } + callback(); + }); + }, function (err) { + main_callback(false); + }); + }; + // any alias + async.any = async.some; + + async.every = function (arr, iterator, main_callback) { + async.forEach(arr, function (x, callback) { + iterator(x, function (v) { + if (!v) { + main_callback(false); + main_callback = function () {}; + } + callback(); + }); + }, function (err) { + main_callback(true); + }); + }; + // all alias + async.all = async.every; + + async.sortBy = function (arr, iterator, callback) { + async.map(arr, function (x, callback) { + iterator(x, function (err, criteria) { + if (err) { + callback(err); + } + else { + callback(null, {value: x, criteria: criteria}); + } + }); + }, function (err, results) { + if (err) { + return callback(err); + } + else { + var fn = function (left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }; + callback(null, _map(results.sort(fn), function (x) { + return x.value; + })); + } + }); + }; + + async.auto = function (tasks, callback) { + callback = callback || function () {}; + var keys = _keys(tasks); + if (!keys.length) { + return callback(null); + } + + var completed = []; + + var listeners = []; + var addListener = function (fn) { + listeners.unshift(fn); + }; + var removeListener = function (fn) { + for (var i = 0; i < listeners.length; i += 1) { + if (listeners[i] === fn) { + listeners.splice(i, 1); + return; + } + } + }; + var taskComplete = function () { + _forEach(listeners, function (fn) { + fn(); + }); + }; + + addListener(function () { + if (completed.length === keys.length) { + callback(null); + } + }); + + _forEach(keys, function (k) { + var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; + var taskCallback = function (err) { + if (err) { + callback(err); + // stop subsequent errors hitting callback multiple times + callback = function () {}; + } + else { + completed.push(k); + taskComplete(); + } + }; + var requires = task.slice(0, Math.abs(task.length - 1)) || []; + var ready = function () { + return _reduce(requires, function (a, x) { + return (a && _indexOf(completed, x) !== -1); + }, true); + }; + if (ready()) { + task[task.length - 1](taskCallback); + } + else { + var listener = function () { + if (ready()) { + removeListener(listener); + task[task.length - 1](taskCallback); + } + }; + addListener(listener); + } + }); + }; + + async.waterfall = function (tasks, callback) { + if (!tasks.length) { + return callback(); + } + callback = callback || function () {}; + var wrapIterator = function (iterator) { + return function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + var args = Array.prototype.slice.call(arguments, 1); + var next = iterator.next(); + if (next) { + args.push(wrapIterator(next)); + } + else { + args.push(callback); + } + async.nextTick(function () { + iterator.apply(null, args); + }); + } + }; + }; + wrapIterator(async.iterator(tasks))(); + }; + + async.parallel = function (tasks, callback) { + callback = callback || function () {}; + if (tasks.constructor === Array) { + async.map(tasks, function (fn, callback) { + if (fn) { + fn(function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + callback.call(null, err, args || null); + }); + } + }, callback); + } + else { + var results = {}; + async.forEach(_keys(tasks), function (k, callback) { + tasks[k](function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + + async.series = function (tasks, callback) { + callback = callback || function () {}; + if (tasks.constructor === Array) { + async.mapSeries(tasks, function (fn, callback) { + if (fn) { + fn(function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + callback.call(null, err, args || null); + }); + } + }, callback); + } + else { + var results = {}; + async.forEachSeries(_keys(tasks), function (k, callback) { + tasks[k](function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + + async.iterator = function (tasks) { + var makeCallback = function (index) { + var fn = function () { + if (tasks.length) { + tasks[index].apply(null, arguments); + } + return fn.next(); + }; + fn.next = function () { + return (index < tasks.length - 1) ? makeCallback(index + 1): null; + }; + return fn; + }; + return makeCallback(0); + }; + + async.apply = function (fn) { + var args = Array.prototype.slice.call(arguments, 1); + return function () { + return fn.apply( + null, args.concat(Array.prototype.slice.call(arguments)) + ); + }; + }; + + var _concat = function (eachfn, arr, fn, callback) { + var r = []; + eachfn(arr, function (x, cb) { + fn(x, function (err, y) { + r = r.concat(y || []); + cb(err); + }); + }, function (err) { + callback(err, r); + }); + }; + async.concat = doParallel(_concat); + async.concatSeries = doSeries(_concat); + + async.whilst = function (test, iterator, callback) { + if (test()) { + iterator(function (err) { + if (err) { + return callback(err); + } + async.whilst(test, iterator, callback); + }); + } + else { + callback(); + } + }; + + async.until = function (test, iterator, callback) { + if (!test()) { + iterator(function (err) { + if (err) { + return callback(err); + } + async.until(test, iterator, callback); + }); + } + else { + callback(); + } + }; + + async.queue = function (worker, concurrency) { + var workers = 0; + var tasks = []; + var q = { + concurrency: concurrency, + push: function (data, callback) { + tasks.push({data: data, callback: callback}); + async.nextTick(q.process); + }, + process: function () { + if (workers < q.concurrency && tasks.length) { + var task = tasks.splice(0, 1)[0]; + workers += 1; + worker(task.data, function () { + workers -= 1; + if (task.callback) { + task.callback.apply(task, arguments); + } + q.process(); + }); + } + }, + length: function () { + return tasks.length; + } + }; + return q; + }; + + var _console_fn = function (name) { + return function (fn) { + var args = Array.prototype.slice.call(arguments, 1); + fn.apply(null, args.concat([function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (typeof console !== 'undefined') { + if (err) { + if (console.error) { + console.error(err); + } + } + else if (console[name]) { + _forEach(args, function (x) { + console[name](x); + }); + } + } + }])); + }; + }; + async.log = _console_fn('log'); + async.dir = _console_fn('dir'); + /*async.info = _console_fn('info'); + async.warn = _console_fn('warn'); + async.error = _console_fn('error');*/ + + async.memoize = function (fn, hasher) { + var memo = {}; + hasher = hasher || function (x) { + return x; + }; + return function () { + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + var key = hasher.apply(null, args); + if (key in memo) { + callback.apply(null, memo[key]); + } + else { + fn.apply(null, args.concat([function () { + memo[key] = arguments; + callback.apply(null, arguments); + }])); + } + }; + }; + +}()); +(function(exports){ +/** + * This file is based on the node.js assert module, but with some small + * changes for browser-compatibility + * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! + */ + + +/** + * Added for browser compatibility + */ + +var _keys = function(obj){ + if(Object.keys) return Object.keys(obj); + if (typeof obj != 'object' && typeof obj != 'function') { + throw new TypeError('-'); + } + var keys = []; + for(var k in obj){ + if(obj.hasOwnProperty(k)) keys.push(k); + } + return keys; +}; + + + +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.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 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. + + +var pSlice = Array.prototype.slice; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = exports; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({message: message, actual: actual, expected: expected}) + +assert.AssertionError = function AssertionError (options) { + this.name = "AssertionError"; + this.message = options.message; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + var stackStartFunction = options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } +}; +// code from util.inherits in node +assert.AssertionError.super_ = Error; + + +// EDITED FOR BROWSER COMPATIBILITY: replaced Object.create call +// TODO: test what effect this may have +var ctor = function () { this.constructor = assert.AssertionError; }; +ctor.prototype = Error.prototype; +assert.AssertionError.prototype = new ctor(); + + +assert.AssertionError.prototype.toString = function() { + if (this.message) { + return [this.name+":", this.message].join(' '); + } else { + return [ this.name+":" + , JSON.stringify(this.expected ) + , this.operator + , JSON.stringify(this.actual) + ].join(" "); + } +}; + +// assert.AssertionError instanceof Error + +assert.AssertionError.__proto__ = Error.prototype; + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +assert.ok = function ok(value, message) { + if (!!!value) fail(value, true, message, "==", assert.ok); +}; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, "==", assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, "!=", assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, "deepEqual", assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3. Other pairs that do not both pass typeof value == "object", + // equivalence is determined by ==. + } else if (typeof actual != 'object' && typeof expected != 'object') { + return actual == expected; + + // 7.4. For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical "prototype" property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isUndefinedOrNull (value) { + return value === null || value === undefined; +} + +function isArguments (object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv (a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // an identical "prototype" property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try{ + var ka = _keys(a), + kb = _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; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, "notDeepEqual", assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, "===", assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as determined by !==. +// assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, "!==", assert.notStrictEqual); + } +}; + +function _throws (shouldThrow, block, err, message) { + var exception = null, + threw = false, + typematters = true; + + message = message || ""; + + //handle optional arguments + if (arguments.length == 3) { + if (typeof(err) == "string") { + message = err; + typematters = false; + } + } else if (arguments.length == 2) { + typematters = false; + } + + try { + block(); + } catch (e) { + threw = true; + exception = e; + } + + if (shouldThrow && !threw) { + fail( "Missing expected exception" + + (err && err.name ? " ("+err.name+")." : '.') + + (message ? " " + message : "") + ); + } + if (!shouldThrow && threw && typematters && exception instanceof err) { + fail( "Got unwanted exception" + + (err && err.name ? " ("+err.name+")." : '.') + + (message ? " " + message : "") + ); + } + if ((shouldThrow && threw && typematters && !(exception instanceof err)) || + (!shouldThrow && threw)) { + throw exception; + } +}; + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function (err) { if (err) {throw err;}}; +})(assert); +(function(exports){ +/*! + * Nodeunit + * Copyright (c) 2010 Caolan McMahon + * MIT Licensed + * + * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! + * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. + * Only code on that line will be removed, its mostly to avoid requiring code + * that is node specific + */ + +/** + * Module dependencies + */ + +//var assert = require('./assert'), //@REMOVE_LINE_FOR_BROWSER +// async = require('../deps/async'); //@REMOVE_LINE_FOR_BROWSER + + +/** + * Creates assertion objects representing the result of an assert call. + * Accepts an object or AssertionError as its argument. + * + * @param {object} obj + * @api public + */ + +exports.assertion = function (obj) { + return { + method: obj.method || '', + message: obj.message || (obj.error && obj.error.message) || '', + error: obj.error, + passed: function () { + return !this.error; + }, + failed: function () { + return Boolean(this.error); + } + }; +}; + +/** + * Creates an assertion list object representing a group of assertions. + * Accepts an array of assertion objects. + * + * @param {Array} arr + * @param {Number} duration + * @api public + */ + +exports.assertionList = function (arr, duration) { + var that = arr || []; + that.failures = function () { + var failures = 0; + for (var i = 0; i < this.length; i += 1) { + if (this[i].failed()) { + failures += 1; + } + } + return failures; + }; + that.passes = function () { + return that.length - that.failures(); + }; + that.duration = duration || 0; + return that; +}; + +/** + * Create a wrapper function for assert module methods. Executes a callback + * after the it's complete with an assertion object representing the result. + * + * @param {Function} callback + * @api private + */ + +var assertWrapper = function (callback) { + return function (new_method, assert_method, arity) { + return function () { + var message = arguments[arity - 1]; + var a = exports.assertion({method: new_method, message: message}); + try { + assert[assert_method].apply(null, arguments); + } + catch (e) { + a.error = e; + } + callback(a); + }; + }; +}; + +/** + * Creates the 'test' object that gets passed to every test function. + * Accepts the name of the test function as its first argument, followed by + * the start time in ms, the options object and a callback function. + * + * @param {String} name + * @param {Number} start + * @param {Object} options + * @param {Function} callback + * @api public + */ + +exports.test = function (name, start, options, callback) { + var expecting; + var a_list = []; + + var wrapAssert = assertWrapper(function (a) { + a_list.push(a); + if (options.log) { + async.nextTick(function () { + options.log(a); + }); + } + }); + + var test = { + done: function (err) { + if (expecting !== undefined && expecting !== a_list.length) { + var e = new Error( + 'Expected ' + expecting + ' assertions, ' + + a_list.length + ' ran' + ); + var a1 = exports.assertion({method: 'expect', error: e}); + a_list.push(a1); + if (options.log) { + async.nextTick(function () { + options.log(a1); + }); + } + } + if (err) { + var a2 = exports.assertion({error: err}); + a_list.push(a2); + if (options.log) { + async.nextTick(function () { + options.log(a2); + }); + } + } + var end = new Date().getTime(); + async.nextTick(function () { + var assertion_list = exports.assertionList(a_list, end - start); + options.testDone(name, assertion_list); + callback(null, a_list); + }); + }, + ok: wrapAssert('ok', 'ok', 2), + same: wrapAssert('same', 'deepEqual', 3), + equals: wrapAssert('equals', 'equal', 3), + expect: function (num) { + expecting = num; + }, + _assertion_list: a_list + }; + // add all functions from the assert module + for (var k in assert) { + if (assert.hasOwnProperty(k)) { + test[k] = wrapAssert(k, k, assert[k].length); + } + } + return test; +}; + +/** + * Ensures an options object has all callbacks, adding empty callback functions + * if any are missing. + * + * @param {Object} opt + * @return {Object} + * @api public + */ + +exports.options = function (opt) { + var optionalCallback = function (name) { + opt[name] = opt[name] || function () {}; + }; + + optionalCallback('moduleStart'); + optionalCallback('moduleDone'); + optionalCallback('testStart'); + optionalCallback('testDone'); + //optionalCallback('log'); + + // 'done' callback is not optional. + + return opt; +}; +})(types); +(function(exports){ +/*! + * Nodeunit + * Copyright (c) 2010 Caolan McMahon + * MIT Licensed + * + * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! + * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. + * Only code on that line will be removed, its mostly to avoid requiring code + * that is node specific + */ + +/** + * Module dependencies + */ + +//var async = require('../deps/async'), //@REMOVE_LINE_FOR_BROWSER +// types = require('./types'); //@REMOVE_LINE_FOR_BROWSER + + +/** + * Added for browser compatibility + */ + +var _keys = function (obj) { + if (Object.keys) { + return Object.keys(obj); + } + var keys = []; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + keys.push(k); + } + } + return keys; +}; + + +var _copy = function (obj) { + var nobj = {}; + var keys = _keys(obj); + for (var i = 0; i < keys.length; i += 1) { + nobj[keys[i]] = obj[keys[i]]; + } + return nobj; +}; + + +/** + * Runs a test function (fn) from a loaded module. After the test function + * calls test.done(), the callback is executed with an assertionList as its + * second argument. + * + * @param {String} name + * @param {Function} fn + * @param {Object} opt + * @param {Function} callback + * @api public + */ + +exports.runTest = function (name, fn, opt, callback) { + var options = types.options(opt); + + options.testStart(name); + var start = new Date().getTime(); + var test = types.test(name, start, options, callback); + + try { + fn(test); + } + catch (e) { + test.done(e); + } +}; + +/** + * Takes an object containing test functions or other test suites as properties + * and runs each in series. After all tests have completed, the callback is + * called with a list of all assertions as the second argument. + * + * If a name is passed to this function it is prepended to all test and suite + * names that run within it. + * + * @param {String} name + * @param {Object} suite + * @param {Object} opt + * @param {Function} callback + * @api public + */ + +exports.runSuite = function (name, suite, opt, callback) { + var keys = _keys(suite); + + async.concatSeries(keys, function (k, cb) { + var prop = suite[k], _name; + + _name = name ? [].concat(name, k) : [k]; + + _name.toString = function () { + // fallback for old one + return this.join(' - '); + }; + + if (typeof prop === 'function') { + var in_name = false; + for (var i = 0; i < _name.length; i += 1) { + if (_name[i] === opt.testspec) { + in_name = true; + } + } + if (!opt.testspec || in_name) { + if (opt.moduleStart) { + opt.moduleStart(); + } + exports.runTest(_name, suite[k], opt, cb); + } + else { + return cb(); + } + } + else { + exports.runSuite(_name, suite[k], opt, cb); + } + }, callback); +}; + +/** + * Run each exported test function or test suite from a loaded module. + * + * @param {String} name + * @param {Object} mod + * @param {Object} opt + * @param {Function} callback + * @api public + */ + +exports.runModule = function (name, mod, opt, callback) { + var options = _copy(types.options(opt)); + + var _run = false; + var _moduleStart = options.moduleStart; + function run_once() { + if (!_run) { + _run = true; + _moduleStart(name); + } + } + options.moduleStart = run_once; + + var start = new Date().getTime(); + + exports.runSuite(null, mod, options, function (err, a_list) { + var end = new Date().getTime(); + var assertion_list = types.assertionList(a_list, end - start); + options.moduleDone(name, assertion_list); + callback(null, a_list); + }); +}; + +/** + * Treats an object literal as a list of modules keyed by name. Runs each + * module and finished with calling 'done'. You can think of this as a browser + * safe alternative to runFiles in the nodeunit module. + * + * @param {Object} modules + * @param {Object} opt + * @api public + */ + +// TODO: add proper unit tests for this function +exports.runModules = function (modules, opt) { + var all_assertions = []; + var options = types.options(opt); + var start = new Date().getTime(); + + async.concatSeries(_keys(modules), function (k, cb) { + exports.runModule(k, modules[k], options, cb); + }, + function (err, all_assertions) { + var end = new Date().getTime(); + options.done(types.assertionList(all_assertions, end - start)); + }); +}; + + +/** + * Wraps a test function with setUp and tearDown functions. + * Used by testCase. + * + * @param {Function} setUp + * @param {Function} tearDown + * @param {Function} fn + * @api private + */ + +var wrapTest = function (setUp, tearDown, fn) { + return function (test) { + var context = {}; + if (tearDown) { + var done = test.done; + test.done = function (err) { + try { + tearDown.call(context, function (err2) { + if (err && err2) { + test._assertion_list.push( + types.assertion({error: err}) + ); + return done(err2); + } + done(err || err2); + }); + } + catch (e) { + done(e); + } + }; + } + if (setUp) { + setUp.call(context, function (err) { + if (err) { + return test.done(err); + } + fn.call(context, test); + }); + } + else { + fn.call(context, test); + } + }; +}; + + +/** + * Wraps a group of tests with setUp and tearDown functions. + * Used by testCase. + * + * @param {Function} setUp + * @param {Function} tearDown + * @param {Object} group + * @api private + */ + +var wrapGroup = function (setUp, tearDown, group) { + var tests = {}; + var keys = _keys(group); + for (var i = 0; i < keys.length; i += 1) { + var k = keys[i]; + if (typeof group[k] === 'function') { + tests[k] = wrapTest(setUp, tearDown, group[k]); + } + else if (typeof group[k] === 'object') { + tests[k] = wrapGroup(setUp, tearDown, group[k]); + } + } + return tests; +}; + + +/** + * Utility for wrapping a suite of test functions with setUp and tearDown + * functions. + * + * @param {Object} suite + * @return {Object} + * @api public + */ + +exports.testCase = function (suite) { + var setUp = suite.setUp; + var tearDown = suite.tearDown; + delete suite.setUp; + delete suite.tearDown; + return wrapGroup(setUp, tearDown, suite); +}; +})(core); +(function(exports){ +/*! + * Nodeunit + * Copyright (c) 2010 Caolan McMahon + * MIT Licensed + * + * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! + * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. + * Only code on that line will be removed, its mostly to avoid requiring code + * that is node specific + */ + + +/** + * NOTE: this test runner is not listed in index.js because it cannot be + * used with the command-line tool, only inside the browser. + */ + + +/** + * Reporter info string + */ + +exports.info = "Browser-based test reporter"; + + +/** + * Run all tests within each module, reporting the results + * + * @param {Array} files + * @api public + */ + +exports.run = function (modules, options) { + var start = new Date().getTime(); + + function setText(el, txt) { + if ('innerText' in el) { + el.innerText = txt; + } + else if ('textContent' in el){ + el.textContent = txt; + } + } + + function getOrCreate(tag, id) { + var el = document.getElementById(id); + if (!el) { + el = document.createElement(tag); + el.id = id; + document.body.appendChild(el); + } + return el; + }; + + var header = getOrCreate('h1', 'nodeunit-header'); + var banner = getOrCreate('h2', 'nodeunit-banner'); + var userAgent = getOrCreate('h2', 'nodeunit-userAgent'); + var tests = getOrCreate('ol', 'nodeunit-tests'); + var result = getOrCreate('p', 'nodeunit-testresult'); + + setText(userAgent, navigator.userAgent); + + nodeunit.runModules(modules, { + moduleStart: function (name) { + /*var mheading = document.createElement('h2'); + mheading.innerText = name; + results.appendChild(mheading); + module = document.createElement('ol'); + results.appendChild(module);*/ + }, + testDone: function (name, assertions) { + var test = document.createElement('li'); + var strong = document.createElement('strong'); + strong.innerHTML = name + ' (' + + '' + assertions.failures() + ', ' + + '' + assertions.passes() + ', ' + + assertions.length + + ')'; + test.className = assertions.failures() ? 'fail': 'pass'; + test.appendChild(strong); + + var aList = document.createElement('ol'); + aList.style.display = 'none'; + test.onclick = function () { + var d = aList.style.display; + aList.style.display = (d == 'none') ? 'block': 'none'; + }; + for (var i=0; i' + (a.error.stack || a.error) + ''; + li.className = 'fail'; + } + else { + li.innerHTML = a.message || a.method || 'no message'; + li.className = 'pass'; + } + aList.appendChild(li); + } + test.appendChild(aList); + tests.appendChild(test); + }, + done: function (assertions) { + var end = new Date().getTime(); + var duration = end - start; + + var failures = assertions.failures(); + banner.className = failures ? 'fail': 'pass'; + + result.innerHTML = 'Tests completed in ' + duration + + ' milliseconds.
' + + assertions.passes() + ' assertions of ' + + '' + assertions.length + ' passed, ' + + assertions.failures() + ' failed.'; + } + }); +}; +})(reporter); +nodeunit = core; +nodeunit.assert = assert; +nodeunit.reporter = reporter; +nodeunit.run = reporter.run; +return nodeunit; })(); diff --git a/node_modules/mongodb/node_modules/bson/test/browser/suite2.js b/node_modules/mongodb/node_modules/bson/test/browser/suite2.js new file mode 100644 index 0000000..c7288e8 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/browser/suite2.js @@ -0,0 +1,13 @@ +this.suite2 = { + 'another test': function (test) { + setTimeout(function () { + // lots of assertions + test.ok(true, 'everythings ok'); + test.ok(true, 'everythings ok'); + test.ok(true, 'everythings ok'); + test.ok(true, 'everythings ok'); + test.ok(true, 'everythings ok'); + test.done(); + }, 10); + } +}; diff --git a/node_modules/mongodb/node_modules/bson/test/browser/suite3.js b/node_modules/mongodb/node_modules/bson/test/browser/suite3.js new file mode 100644 index 0000000..8929741 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/browser/suite3.js @@ -0,0 +1,7 @@ +this.suite3 = { + 'test for ie6,7,8': function (test) { + test.deepEqual(["test"], ["test"]); + test.notDeepEqual(["a"], ["b"]); + test.done(); + } +}; diff --git a/node_modules/mongodb/node_modules/bson/test/browser/test.html b/node_modules/mongodb/node_modules/bson/test/browser/test.html new file mode 100644 index 0000000..56d4d96 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/browser/test.html @@ -0,0 +1,30 @@ + + + Example tests + + + + + + + + + + + + + + + + + + + + + + + diff --git a/node_modules/mongodb/node_modules/bson/test/node/bson_array_test.js b/node_modules/mongodb/node_modules/bson/test/node/bson_array_test.js new file mode 100644 index 0000000..5304bef --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/node/bson_array_test.js @@ -0,0 +1,240 @@ +var mongodb = require('../../lib/bson').pure(); + +var testCase = require('nodeunit').testCase, + mongoO = require('../../lib/bson').pure(), + debug = require('util').debug, + inspect = require('util').inspect, + Buffer = require('buffer').Buffer, + gleak = require('../../tools/gleak'), + fs = require('fs'), + BSON = mongoO.BSON, + Code = mongoO.Code, + Binary = mongoO.Binary, + Timestamp = mongoO.Timestamp, + Long = mongoO.Long, + MongoReply = mongoO.MongoReply, + ObjectID = mongoO.ObjectID, + Symbol = mongoO.Symbol, + DBRef = mongoO.DBRef, + Double = mongoO.Double, + MinKey = mongoO.MinKey, + MaxKey = mongoO.MaxKey, + BinaryParser = mongoO.BinaryParser, + utils = require('./tools/utils'); + +var BSONSE = mongodb, + BSONDE = mongodb; + +// for tests +BSONDE.BSON_BINARY_SUBTYPE_DEFAULT = 0; +BSONDE.BSON_BINARY_SUBTYPE_FUNCTION = 1; +BSONDE.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +BSONDE.BSON_BINARY_SUBTYPE_UUID = 3; +BSONDE.BSON_BINARY_SUBTYPE_MD5 = 4; +BSONDE.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; + +BSONSE.BSON_BINARY_SUBTYPE_DEFAULT = 0; +BSONSE.BSON_BINARY_SUBTYPE_FUNCTION = 1; +BSONSE.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +BSONSE.BSON_BINARY_SUBTYPE_UUID = 3; +BSONSE.BSON_BINARY_SUBTYPE_MD5 = 4; +BSONSE.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; + +var hexStringToBinary = function(string) { + var numberofValues = string.length / 2; + var array = ""; + + for(var i = 0; i < numberofValues; i++) { + array += String.fromCharCode(parseInt(string[i*2] + string[i*2 + 1], 16)); + } + return array; +} + +var assertBuffersEqual = function(test, buffer1, buffer2) { + if(buffer1.length != buffer2.length) test.fail("Buffers do not have the same length", buffer1, buffer2); + + for(var i = 0; i < buffer1.length; i++) { + test.equal(buffer1[i], buffer2[i]); + } +} + +/** + * Module for parsing an ISO 8601 formatted string into a Date object. + */ +var ISODate = function (string) { + var match; + + if (typeof string.getTime === "function") + return string; + else if (match = string.match(/^(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|((\+|-)(\d{2}):(\d{2}))))?)?)?$/)) { + var date = new Date(); + date.setUTCFullYear(Number(match[1])); + date.setUTCMonth(Number(match[3]) - 1 || 0); + date.setUTCDate(Number(match[5]) || 0); + date.setUTCHours(Number(match[7]) || 0); + date.setUTCMinutes(Number(match[8]) || 0); + date.setUTCSeconds(Number(match[10]) || 0); + date.setUTCMilliseconds(Number("." + match[12]) * 1000 || 0); + + if (match[13] && match[13] !== "Z") { + var h = Number(match[16]) || 0, + m = Number(match[17]) || 0; + + h *= 3600000; + m *= 60000; + + var offset = h + m; + if (match[15] == "+") + offset = -offset; + + date = new Date(date.valueOf() + offset); + } + + return date; + } else + throw new Error("Invalid ISO 8601 date given.", __filename); +}; + +var _Uint8Array = null; + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.setUp = function(callback) { + _Uint8Array = global.Uint8Array; + delete global['Uint8Array']; + callback(); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.tearDown = function(callback) { + global['Uint8Array'] = _Uint8Array; + callback(); +} + +// /** +// * @ignore +// */ +// exports.shouldCorrectlyDeserializeUsingTypedArray = function(test) { +// var motherOfAllDocuments = { +// 'string': '客家话', +// 'array': [1,2,3], +// 'hash': {'a':1, 'b':2}, +// 'date': new Date(), +// 'oid': new ObjectID(), +// 'binary': new Binary(new Buffer("hello")), +// 'int': 42, +// 'float': 33.3333, +// 'regexp': /regexp/, +// 'boolean': true, +// 'long': Long.fromNumber(100), +// 'where': new Code('this.a > i', {i:1}), +// 'dbref': new DBRef('namespace', new ObjectID(), 'integration_tests_'), +// 'minkey': new MinKey(), +// 'maxkey': new MaxKey() +// } +// +// // Let's serialize it +// var data = BSONSE.BSON.serialize(motherOfAllDocuments, true, true, false); +// // Build a typed array +// var arr = new Uint8Array(new ArrayBuffer(data.length)); +// // Iterate over all the fields and copy +// for(var i = 0; i < data.length; i++) { +// arr[i] = data[i] +// } +// +// // Deserialize the object +// var object = BSONDE.BSON.deserialize(arr); +// // Asserts +// test.equal(motherOfAllDocuments.string, object.string); +// test.deepEqual(motherOfAllDocuments.array, object.array); +// test.deepEqual(motherOfAllDocuments.date, object.date); +// test.deepEqual(motherOfAllDocuments.oid.toHexString(), object.oid.toHexString()); +// test.deepEqual(motherOfAllDocuments.binary.length(), object.binary.length()); +// // Assert the values of the binary +// for(var i = 0; i < motherOfAllDocuments.binary.length(); i++) { +// test.equal(motherOfAllDocuments.binary.value[i], object.binary[i]); +// } +// test.deepEqual(motherOfAllDocuments.int, object.int); +// test.deepEqual(motherOfAllDocuments.float, object.float); +// test.deepEqual(motherOfAllDocuments.regexp, object.regexp); +// test.deepEqual(motherOfAllDocuments.boolean, object.boolean); +// test.deepEqual(motherOfAllDocuments.long.toNumber(), object.long); +// test.deepEqual(motherOfAllDocuments.where, object.where); +// test.deepEqual(motherOfAllDocuments.dbref.oid.toHexString(), object.dbref.oid.toHexString()); +// test.deepEqual(motherOfAllDocuments.dbref.namespace, object.dbref.namespace); +// test.deepEqual(motherOfAllDocuments.dbref.db, object.dbref.db); +// test.deepEqual(motherOfAllDocuments.minkey, object.minkey); +// test.deepEqual(motherOfAllDocuments.maxkey, object.maxkey); +// test.done(); +// } + +/** + * @ignore + */ +exports.shouldCorrectlySerializeUsingTypedArray = function(test) { + var motherOfAllDocuments = { + 'string': 'hello', + 'array': [1,2,3], + 'hash': {'a':1, 'b':2}, + 'date': new Date(), + 'oid': new ObjectID(), + 'binary': new Binary(new Buffer("hello")), + 'int': 42, + 'float': 33.3333, + 'regexp': /regexp/, + 'boolean': true, + 'long': Long.fromNumber(100), + 'where': new Code('this.a > i', {i:1}), + 'dbref': new DBRef('namespace', new ObjectID(), 'integration_tests_'), + 'minkey': new MinKey(), + 'maxkey': new MaxKey() + } + + // Let's serialize it + var data = BSONSE.BSON.serialize(motherOfAllDocuments, true, false, false); + // And deserialize it again + var object = BSONSE.BSON.deserialize(data); + // Asserts + test.equal(motherOfAllDocuments.string, object.string); + test.deepEqual(motherOfAllDocuments.array, object.array); + test.deepEqual(motherOfAllDocuments.date, object.date); + test.deepEqual(motherOfAllDocuments.oid.toHexString(), object.oid.toHexString()); + test.deepEqual(motherOfAllDocuments.binary.length(), object.binary.length()); + // Assert the values of the binary + for(var i = 0; i < motherOfAllDocuments.binary.length(); i++) { + test.equal(motherOfAllDocuments.binary.value[i], object.binary[i]); + } + test.deepEqual(motherOfAllDocuments.int, object.int); + test.deepEqual(motherOfAllDocuments.float, object.float); + test.deepEqual(motherOfAllDocuments.regexp, object.regexp); + test.deepEqual(motherOfAllDocuments.boolean, object.boolean); + test.deepEqual(motherOfAllDocuments.long.toNumber(), object.long); + test.deepEqual(motherOfAllDocuments.where, object.where); + test.deepEqual(motherOfAllDocuments.dbref.oid.toHexString(), object.dbref.oid.toHexString()); + test.deepEqual(motherOfAllDocuments.dbref.namespace, object.dbref.namespace); + test.deepEqual(motherOfAllDocuments.dbref.db, object.dbref.db); + test.deepEqual(motherOfAllDocuments.minkey, object.minkey); + test.deepEqual(motherOfAllDocuments.maxkey, object.maxkey); + test.done(); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.noGlobalsLeaked = function(test) { + var leaks = gleak.detectNew(); + test.equal(0, leaks.length, "global var leak detected: " + leaks.join(', ')); + test.done(); +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/test/node/bson_parser_comparision_test.js b/node_modules/mongodb/node_modules/bson/test/node/bson_parser_comparision_test.js new file mode 100644 index 0000000..e217be9 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/node/bson_parser_comparision_test.js @@ -0,0 +1,459 @@ +var sys = require('util'), + debug = require('util').debug, + inspect = require('util').inspect, + Buffer = require('buffer').Buffer, + BSON = require('../../ext/bson').BSON, + Buffer = require('buffer').Buffer, + BSONJS = require('../../lib/bson/bson').BSON, + BinaryParser = require('../../lib/bson/binary_parser').BinaryParser, + Long = require('../../lib/bson/long').Long, + ObjectID = require('../../lib/bson/bson').ObjectID, + Binary = require('../../lib/bson/bson').Binary, + Code = require('../../lib/bson/bson').Code, + DBRef = require('../../lib/bson/bson').DBRef, + Symbol = require('../../lib/bson/bson').Symbol, + Double = require('../../lib/bson/bson').Double, + MaxKey = require('../../lib/bson/bson').MaxKey, + MinKey = require('../../lib/bson/bson').MinKey, + Timestamp = require('../../lib/bson/bson').Timestamp, + assert = require('assert'); + +// Parsers +var bsonC = new BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); +var bsonJS = new BSONJS([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.setUp = function(callback) { + callback(); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.tearDown = function(callback) { + callback(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize simple edge value'] = function(test) { + // Simple serialization and deserialization of edge value + var doc = {doc:0x1ffffffffffffe}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + + var doc = {doc:-0x1ffffffffffffe}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly execute toJSON'] = function(test) { + var a = Long.fromNumber(10); + assert.equal(10, a); + + var a = Long.fromNumber(9223372036854775807); + assert.equal(9223372036854775807, a); + + // Simple serialization and deserialization test for a Single String value + var doc = {doc:'Serialize'}; + var simple_string_serialized = bsonC.serialize(doc, true, false); + + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Serialize and Deserialize nested document'] = function(test) { + // Nested doc + var doc = {a:{b:{c:1}}}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Simple integer serialization/deserialization test, including testing boundary conditions'] = function(test) { + var doc = {doc:-1}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + + var doc = {doc:2147483648}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + + var doc = {doc:-2147483648}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization test for a Long value'] = function(test) { + var doc = {doc:Long.fromNumber(9223372036854775807)}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize({doc:Long.fromNumber(9223372036854775807)}, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + + var doc = {doc:Long.fromNumber(-9223372036854775807)}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize({doc:Long.fromNumber(-9223372036854775807)}, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a Float value'] = function(test) { + var doc = {doc:2222.3333}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + + var doc = {doc:-2222.3333}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a null value'] = function(test) { + var doc = {doc:null}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a boolean value'] = function(test) { + var doc = {doc:true}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a date value'] = function(test) { + var date = new Date(); + var doc = {doc:date}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')), bsonC.deserialize(simple_string_serialized)); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a boolean value'] = function(test) { + var doc = {doc:/abcd/mi}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.equal(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString(), bsonC.deserialize(simple_string_serialized).doc.toString()); + + var doc = {doc:/abcd/}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc, false, true)); + assert.equal(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString(), bsonC.deserialize(simple_string_serialized).doc.toString()); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a objectId value'] = function(test) { + var doc = {doc:new ObjectID()}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + var doc2 = {doc:ObjectID.createFromHexString(doc.doc.toHexString())}; + + assert.deepEqual(simple_string_serialized, bsonJS.serialize(doc2, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.toString(), bsonC.deserialize(simple_string_serialized).doc.toString()); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a Binary value'] = function(test) { + var binary = new Binary(); + var string = 'binstring' + for(var index = 0; index < string.length; index++) { binary.put(string.charAt(index)); } + + var simple_string_serialized = bsonC.serialize({doc:binary}, false, true); + assert.deepEqual(simple_string_serialized, bsonJS.serialize({doc:binary}, false, true)); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized, 'binary')).doc.value(), bsonC.deserialize(simple_string_serialized).doc.value()); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a Code value'] = function(test) { + var code = new Code('this.a > i', {'i': 1}); + var simple_string_serialized_2 = bsonJS.serialize({doc:code}, false, true); + var simple_string_serialized = bsonC.serialize({doc:code}, false, true); + + assert.deepEqual(simple_string_serialized, simple_string_serialized_2); + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')).doc.scope, bsonC.deserialize(simple_string_serialized).doc.scope); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for an Object'] = function(test) { + var simple_string_serialized = bsonC.serialize({doc:{a:1, b:{c:2}}}, false, true); + var simple_string_serialized_2 = bsonJS.serialize({doc:{a:1, b:{c:2}}}, false, true); + assert.deepEqual(simple_string_serialized, simple_string_serialized_2) + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')).doc, bsonC.deserialize(simple_string_serialized).doc); + + // Simple serialization and deserialization for an Array + var simple_string_serialized = bsonC.serialize({doc:[9, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1]}, false, true); + var simple_string_serialized_2 = bsonJS.serialize({doc:[9, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1]}, false, true); + + assert.deepEqual(simple_string_serialized, simple_string_serialized_2) + assert.deepEqual(bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')).doc, bsonC.deserialize(simple_string_serialized).doc); + test.done(); +} + +/** + * @ignore + */ +exports['Simple serialization and deserialization for a DBRef'] = function(test) { + var oid = new ObjectID() + var oid2 = new ObjectID.createFromHexString(oid.toHexString()) + var simple_string_serialized = bsonJS.serialize({doc:new DBRef('namespace', oid2, 'integration_tests_')}, false, true); + var simple_string_serialized_2 = bsonC.serialize({doc:new DBRef('namespace', oid, 'integration_tests_')}, false, true); + + assert.deepEqual(simple_string_serialized, simple_string_serialized_2) + // Ensure we have the same values for the dbref + var object_js = bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')); + var object_c = bsonC.deserialize(simple_string_serialized); + + assert.equal(object_js.doc.namespace, object_c.doc.namespace); + assert.equal(object_js.doc.oid.toHexString(), object_c.doc.oid.toHexString()); + assert.equal(object_js.doc.db, object_c.doc.db); + test.done(); +} + +/** + * @ignore + */ +exports['Should correctly deserialize bytes array'] = function(test) { + // Serialized document + var bytes = [47,0,0,0,2,110,97,109,101,0,6,0,0,0,80,97,116,116,121,0,16,97,103,101,0,34,0,0,0,7,95,105,100,0,76,100,12,23,11,30,39,8,89,0,0,1,0]; + var serialized_data = ''; + // Convert to chars + for(var i = 0; i < bytes.length; i++) { + serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); + } + var object = bsonC.deserialize(new Buffer(serialized_data, 'binary')); + assert.equal('Patty', object.name) + assert.equal(34, object.age) + assert.equal('4c640c170b1e270859000001', object._id.toHexString()) + test.done(); +} + +/** + * @ignore + */ +exports['Serialize utf8'] = function(test) { + var doc = { "name" : "本荘由利地域に洪水警報", "name1" : "öüóőúéáűíÖÜÓŐÚÉÁŰÍ", "name2" : "abcdedede"}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + var simple_string_serialized2 = bsonJS.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, simple_string_serialized2) + + var object = bsonC.deserialize(simple_string_serialized); + assert.equal(doc.name, object.name) + assert.equal(doc.name1, object.name1) + assert.equal(doc.name2, object.name2) + test.done(); +} + +/** + * @ignore + */ +exports['Serialize object with array'] = function(test) { + var doc = {b:[1, 2, 3]}; + var simple_string_serialized = bsonC.serialize(doc, false, true); + var simple_string_serialized_2 = bsonJS.serialize(doc, false, true); + assert.deepEqual(simple_string_serialized, simple_string_serialized_2) + + var object = bsonC.deserialize(simple_string_serialized); + assert.deepEqual(doc, object) + test.done(); +} + +/** + * @ignore + */ +exports['Test equality of an object ID'] = function(test) { + var object_id = new ObjectID(); + var object_id_2 = new ObjectID(); + assert.ok(object_id.equals(object_id)); + assert.ok(!(object_id.equals(object_id_2))) + test.done(); +} + +/** + * @ignore + */ +exports['Test same serialization for Object ID'] = function(test) { + var object_id = new ObjectID(); + var object_id2 = ObjectID.createFromHexString(object_id.toString()) + var simple_string_serialized = bsonJS.serialize({doc:object_id}, false, true); + var simple_string_serialized_2 = bsonC.serialize({doc:object_id2}, false, true); + + assert.equal(simple_string_serialized_2.length, simple_string_serialized.length); + assert.deepEqual(simple_string_serialized, simple_string_serialized_2) + var object = bsonJS.deserialize(new Buffer(simple_string_serialized_2, 'binary')); + var object2 = bsonC.deserialize(simple_string_serialized); + assert.equal(object.doc.id, object2.doc.id) + test.done(); +} + +/** + * @ignore + */ +exports['Complex object serialization'] = function(test) { + // JS Object + var c1 = { _id: new ObjectID, comments: [], title: 'number 1' }; + var c2 = { _id: new ObjectID, comments: [], title: 'number 2' }; + var doc = { + numbers: [] + , owners: [] + , comments: [c1, c2] + , _id: new ObjectID + }; + + var simple_string_serialized = bsonJS.serialize(doc, false, true); + + // C++ Object + var c1 = { _id: ObjectID.createFromHexString(c1._id.toHexString()), comments: [], title: 'number 1' }; + var c2 = { _id: ObjectID.createFromHexString(c2._id.toHexString()), comments: [], title: 'number 2' }; + var doc = { + numbers: [] + , owners: [] + , comments: [c1, c2] + , _id: ObjectID.createFromHexString(doc._id.toHexString()) + }; + + var simple_string_serialized_2 = bsonC.serialize(doc, false, true); + + for(var i = 0; i < simple_string_serialized_2.length; i++) { + // debug(i + "[" + simple_string_serialized_2[i] + "] = [" + simple_string_serialized[i] + "]") + assert.equal(simple_string_serialized_2[i], simple_string_serialized[i]); + } + + var doc1 = bsonJS.deserialize(new Buffer(simple_string_serialized_2)); + var doc2 = bsonC.deserialize(new Buffer(simple_string_serialized_2)); + assert.equal(doc._id.id, doc1._id.id) + assert.equal(doc._id.id, doc2._id.id) + assert.equal(doc1._id.id, doc2._id.id) + + var doc = { + _id: 'testid', + key1: { code: 'test1', time: {start:1309323402727,end:1309323402727}, x:10, y:5 }, + key2: { code: 'test1', time: {start:1309323402727,end:1309323402727}, x:10, y:5 } + }; + + var simple_string_serialized = bsonJS.serialize(doc, false, true); + var simple_string_serialized_2 = bsonC.serialize(doc, false, true); + test.done(); +} + +/** + * @ignore + */ +exports['Serialize function'] = function(test) { + var doc = { + _id: 'testid', + key1: function() {} + } + + var simple_string_serialized = bsonJS.serialize(doc, false, true, true); + var simple_string_serialized_2 = bsonC.serialize(doc, false, true, true); + + // Deserialize the string + var doc1 = bsonJS.deserialize(new Buffer(simple_string_serialized_2)); + var doc2 = bsonC.deserialize(new Buffer(simple_string_serialized_2)); + assert.equal(doc1.key1.code.toString(), doc2.key1.code.toString()) + test.done(); +} + +/** + * @ignore + */ +exports['Serialize document with special operators'] = function(test) { + var doc = {"user_id":"4e9fc8d55883d90100000003","lc_status":{"$ne":"deleted"},"owner_rating":{"$exists":false}}; + var simple_string_serialized = bsonJS.serialize(doc, false, true, true); + var simple_string_serialized_2 = bsonC.serialize(doc, false, true, true); + + // Should serialize to the same value + assert.equal(simple_string_serialized_2.toString('base64'), simple_string_serialized.toString('base64')) + var doc1 = bsonJS.deserialize(simple_string_serialized_2); + var doc2 = bsonC.deserialize(simple_string_serialized); + assert.deepEqual(doc1, doc2) + test.done(); +} + +/** + * @ignore + */ +exports['Create ObjectID from hex string'] = function(test) { + // Hex Id + var hexId = new ObjectID().toString(); + var docJS = {_id: ObjectID.createFromHexString(hexId), 'funds.remaining': {$gte: 1.222}, 'transactions.id': {$ne: ObjectID.createFromHexString(hexId)}}; + var docC = {_id: ObjectID.createFromHexString(hexId), 'funds.remaining': {$gte: 1.222}, 'transactions.id': {$ne: ObjectID.createFromHexString(hexId)}}; + var docJSBin = bsonJS.serialize(docJS, false, true, true); + var docCBin = bsonC.serialize(docC, false, true, true); + assert.equal(docCBin.toString('base64'), docJSBin.toString('base64')); + test.done(); +} + +/** + * @ignore + */ +exports['Serialize big complex document'] = function(test) { + // Complex document serialization + var doc = {"DateTime": "Tue Nov 40 2011 17:27:55 GMT+0000 (WEST)","isActive": true,"Media": {"URL": "http://videos.sapo.pt/Tc85NsjaKjj8o5aV7Ubb"},"Title": "Lisboa fecha a ganhar 0.19%","SetPosition": 60,"Type": "videos","Thumbnail": [{"URL": "http://rd3.videos.sapo.pt/Tc85NsjaKjj8o5aV7Ubb/pic/320x240","Dimensions": {"Height": 240,"Width": 320}}],"Source": {"URL": "http://videos.sapo.pt","SetID": "1288","SourceID": "http://videos.sapo.pt/tvnet/rss2","SetURL": "http://noticias.sapo.pt/videos/tv-net_1288/","ItemID": "Tc85NsjaKjj8o5aV7Ubb","Name": "SAPO Vídeos"},"Category": "Tec_ciencia","Description": "Lisboa fecha a ganhar 0.19%","GalleryID": new ObjectID("4eea2a634ce8573200000000"),"InternalRefs": {"RegisterDate": "Thu Dec 15 2011 17:12:51 GMT+0000 (WEST)","ChangeDate": "Thu Dec 15 2011 17:12:51 GMT+0000 (WEST)","Hash": 332279244514},"_id": new ObjectID("4eea2a96e52778160000003a")} + var docJSBin = bsonJS.serialize(doc, false, true, true); + var docCBin = bsonC.serialize(doc, false, true, true); + assert.equal(docCBin.toString('base64'), docJSBin.toString('base64')); + test.done(); +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/test/node/bson_test.js b/node_modules/mongodb/node_modules/bson/test/node/bson_test.js new file mode 100644 index 0000000..d21f3dd --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/node/bson_test.js @@ -0,0 +1,1591 @@ +var mongodb = process.env['TEST_NATIVE'] != null ? require('../../lib/bson').native() : require('../../lib/bson').pure(); + +var testCase = require('nodeunit').testCase, + mongoO = require('../../lib/bson').pure(), + Buffer = require('buffer').Buffer, + gleak = require('../../tools/gleak'), + fs = require('fs'), + BSON = mongoO.BSON, + Code = mongoO.Code, + Binary = mongoO.Binary, + Timestamp = mongoO.Timestamp, + Long = mongoO.Long, + MongoReply = mongoO.MongoReply, + ObjectID = mongoO.ObjectID, + Symbol = mongoO.Symbol, + DBRef = mongoO.DBRef, + Double = mongoO.Double, + MinKey = mongoO.MinKey, + MaxKey = mongoO.MaxKey, + BinaryParser = mongoO.BinaryParser; + +var BSONSE = mongodb, + BSONDE = mongodb; + +// for tests +BSONDE.BSON_BINARY_SUBTYPE_DEFAULT = 0; +BSONDE.BSON_BINARY_SUBTYPE_FUNCTION = 1; +BSONDE.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +BSONDE.BSON_BINARY_SUBTYPE_UUID = 3; +BSONDE.BSON_BINARY_SUBTYPE_MD5 = 4; +BSONDE.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; + +BSONSE.BSON_BINARY_SUBTYPE_DEFAULT = 0; +BSONSE.BSON_BINARY_SUBTYPE_FUNCTION = 1; +BSONSE.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +BSONSE.BSON_BINARY_SUBTYPE_UUID = 3; +BSONSE.BSON_BINARY_SUBTYPE_MD5 = 4; +BSONSE.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; + +var hexStringToBinary = function(string) { + var numberofValues = string.length / 2; + var array = ""; + + for(var i = 0; i < numberofValues; i++) { + array += String.fromCharCode(parseInt(string[i*2] + string[i*2 + 1], 16)); + } + return array; +} + +var assertBuffersEqual = function(test, buffer1, buffer2) { + if(buffer1.length != buffer2.length) test.fail("Buffers do not have the same length", buffer1, buffer2); + + for(var i = 0; i < buffer1.length; i++) { + test.equal(buffer1[i], buffer2[i]); + } +} + +/** + * Module for parsing an ISO 8601 formatted string into a Date object. + */ +var ISODate = function (string) { + var match; + + if (typeof string.getTime === "function") + return string; + else if (match = string.match(/^(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|((\+|-)(\d{2}):(\d{2}))))?)?)?$/)) { + var date = new Date(); + date.setUTCFullYear(Number(match[1])); + date.setUTCMonth(Number(match[3]) - 1 || 0); + date.setUTCDate(Number(match[5]) || 0); + date.setUTCHours(Number(match[7]) || 0); + date.setUTCMinutes(Number(match[8]) || 0); + date.setUTCSeconds(Number(match[10]) || 0); + date.setUTCMilliseconds(Number("." + match[12]) * 1000 || 0); + + if (match[13] && match[13] !== "Z") { + var h = Number(match[16]) || 0, + m = Number(match[17]) || 0; + + h *= 3600000; + m *= 60000; + + var offset = h + m; + if (match[15] == "+") + offset = -offset; + + date = new Date(date.valueOf() + offset); + } + + return date; + } else + throw new Error("Invalid ISO 8601 date given.", __filename); +}; + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.setUp = function(callback) { + callback(); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.tearDown = function(callback) { + callback(); +} + +/** + * @ignore + */ +exports['Should Correctly get BSON types from require'] = function(test) { + var _mongodb = require('../../lib/bson'); + test.ok(_mongodb.ObjectID === ObjectID); + test.ok(_mongodb.Binary === Binary); + test.ok(_mongodb.Long === Long); + test.ok(_mongodb.Timestamp === Timestamp); + test.ok(_mongodb.Code === Code); + test.ok(_mongodb.DBRef === DBRef); + test.ok(_mongodb.Symbol === Symbol); + test.ok(_mongodb.MinKey === MinKey); + test.ok(_mongodb.MaxKey === MaxKey); + test.ok(_mongodb.Double === Double); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Deserialize object'] = function(test) { + var bytes = [95,0,0,0,2,110,115,0,42,0,0,0,105,110,116,101,103,114,97,116,105,111,110,95,116,101,115,116,115,95,46,116,101,115,116,95,105,110,100,101,120,95,105,110,102,111,114,109,97,116,105,111,110,0,8,117,110,105,113,117,101,0,0,3,107,101,121,0,12,0,0,0,16,97,0,1,0,0,0,0,2,110,97,109,101,0,4,0,0,0,97,95,49,0,0]; + var serialized_data = ''; + // Convert to chars + for(var i = 0; i < bytes.length; i++) { + serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); + } + + var object = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(new Buffer(serialized_data, 'binary')); + test.equal("a_1", object.name); + test.equal(false, object.unique); + test.equal(1, object.key.a); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Deserialize object with all types'] = function(test) { + var bytes = [26,1,0,0,7,95,105,100,0,161,190,98,75,118,169,3,0,0,3,0,0,4,97,114,114,97,121,0,26,0,0,0,16,48,0,1,0,0,0,16,49,0,2,0,0,0,16,50,0,3,0,0,0,0,2,115,116,114,105,110,103,0,6,0,0,0,104,101,108,108,111,0,3,104,97,115,104,0,19,0,0,0,16,97,0,1,0,0,0,16,98,0,2,0,0,0,0,9,100,97,116,101,0,161,190,98,75,0,0,0,0,7,111,105,100,0,161,190,98,75,90,217,18,0,0,1,0,0,5,98,105,110,97,114,121,0,7,0,0,0,2,3,0,0,0,49,50,51,16,105,110,116,0,42,0,0,0,1,102,108,111,97,116,0,223,224,11,147,169,170,64,64,11,114,101,103,101,120,112,0,102,111,111,98,97,114,0,105,0,8,98,111,111,108,101,97,110,0,1,15,119,104,101,114,101,0,25,0,0,0,12,0,0,0,116,104,105,115,46,120,32,61,61,32,51,0,5,0,0,0,0,3,100,98,114,101,102,0,37,0,0,0,2,36,114,101,102,0,5,0,0,0,116,101,115,116,0,7,36,105,100,0,161,190,98,75,2,180,1,0,0,2,0,0,0,10,110,117,108,108,0,0]; + var serialized_data = ''; + // Convert to chars + for(var i = 0; i < bytes.length; i++) { + serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); + } + + var object = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(new Buffer(serialized_data, 'binary'));//, false, true); + // Perform tests + test.equal("hello", object.string); + test.deepEqual([1,2,3], object.array); + test.equal(1, object.hash.a); + test.equal(2, object.hash.b); + test.ok(object.date != null); + test.ok(object.oid != null); + test.ok(object.binary != null); + test.equal(42, object.int); + test.equal(33.3333, object.float); + test.ok(object.regexp != null); + test.equal(true, object.boolean); + test.ok(object.where != null); + test.ok(object.dbref != null); + test.ok(object[null] == null); + test.done(); +} + +/** + * @ignore + */ +exports['Should Serialize and Deserialize String'] = function(test) { + var test_string = {hello: 'world'}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_string, false, true); + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_string)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_string, false, serialized_data2, 0); + + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + test.deepEqual(test_string, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Serialize and Deserialize Empty String'] = function(test) { + var test_string = {hello: ''}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_string, false, true); + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_string)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_string, false, serialized_data2, 0); + + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + test.deepEqual(test_string, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Integer'] = function(test) { + var test_number = {doc: 5}; + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_number, false, true); + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_number)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_number, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + test.deepEqual(test_number, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data)); + test.deepEqual(test_number, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data2)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize null value'] = function(test) { + var test_null = {doc:null}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_null, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_null)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_null, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var object = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.equal(null, object.doc); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Number'] = function(test) { + var test_number = {doc: 5.5}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_number, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_number)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_number, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + test.deepEqual(test_number, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Integer'] = function(test) { + var test_int = {doc: 42}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_int, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_int)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_int, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + test.deepEqual(test_int.doc, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc); + + test_int = {doc: -5600}; + serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_int, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_int)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_int, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + test.deepEqual(test_int.doc, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc); + + test_int = {doc: 2147483647}; + serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_int, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_int)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_int, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + test.deepEqual(test_int.doc, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc); + + test_int = {doc: -2147483648}; + serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_int, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_int)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_int, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + test.deepEqual(test_int.doc, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Object'] = function(test) { + var doc = {doc: {age: 42, name: 'Spongebob', shoe_size: 9.5}}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + test.deepEqual(doc.doc.age, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc.age); + test.deepEqual(doc.doc.name, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc.name); + test.deepEqual(doc.doc.shoe_size, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc.shoe_size); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Array'] = function(test) { + var doc = {doc: [1, 2, 'a', 'b']}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.equal(doc.doc[0], deserialized.doc[0]) + test.equal(doc.doc[1], deserialized.doc[1]) + test.equal(doc.doc[2], deserialized.doc[2]) + test.equal(doc.doc[3], deserialized.doc[3]) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Array with added on functions'] = function(test) { + Array.prototype.toXml = function() {}; + var doc = {doc: [1, 2, 'a', 'b']}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.equal(doc.doc[0], deserialized.doc[0]) + test.equal(doc.doc[1], deserialized.doc[1]) + test.equal(doc.doc[2], deserialized.doc[2]) + test.equal(doc.doc[3], deserialized.doc[3]) + test.done(); +} + +/** + * @ignore + */ +exports['Should correctly deserialize a nested object'] = function(test) { + var doc = {doc: {doc:1}}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + test.deepEqual(doc.doc.doc, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc.doc); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize A Boolean'] = function(test) { + var doc = {doc: true}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + test.equal(doc.doc, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize a Date'] = function(test) { + var date = new Date(); + //(2009, 11, 12, 12, 00, 30) + date.setUTCDate(12); + date.setUTCFullYear(2009); + date.setUTCMonth(11 - 1); + date.setUTCHours(12); + date.setUTCMinutes(0); + date.setUTCSeconds(30); + var doc = {doc: date}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + test.equal(doc.date, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc.date); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize nested doc'] = function(test) { + var doc = { + string: "Strings are great", + decimal: 3.14159265, + bool: true, + integer: 5, + + subObject: { + moreText: "Bacon ipsum dolor.", + longKeylongKeylongKeylongKeylongKeylongKey: "Pork belly." + }, + + subArray: [1,2,3,4,5,6,7,8,9,10], + anotherString: "another string" + } + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Oid'] = function(test) { + var doc = {doc: new ObjectID()}; + var doc2 = {doc: ObjectID.createFromHexString(doc.doc.toHexString())}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + delete doc.doc.__id; + test.deepEqual(doc, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly encode Empty Hash'] = function(test) { + var doc = {}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + test.deepEqual(doc, new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Ordered Hash'] = function(test) { + var doc = {doc: {b:1, a:2, c:3, d:4}}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var decoded_hash = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data).doc; + var keys = []; + + for(var name in decoded_hash) keys.push(name); + test.deepEqual(['b', 'a', 'c', 'd'], keys); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Regular Expression'] = function(test) { + // Serialize the regular expression + var doc = {doc: /foobar/mi}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + + test.deepEqual(doc.doc.toString(), doc2.doc.toString()); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize a Binary object'] = function(test) { + var bin = new Binary(); + var string = 'binstring'; + for(var index = 0; index < string.length; index++) { + bin.put(string.charAt(index)); + } + + var doc = {doc: bin}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + + test.deepEqual(doc.doc.value(), deserialized_data.doc.value()); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize a big Binary object'] = function(test) { + var data = fs.readFileSync("test/node/data/test_gs_weird_bug.png", 'binary'); + var bin = new Binary(); + bin.write(data); + var doc = {doc: bin}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.doc.value(), deserialized_data.doc.value()); + test.done(); +} + +/** + * @ignore + */ +exports["Should Correctly Serialize and Deserialize DBRef"] = function(test) { + var oid = new ObjectID(); + var doc = {dbref: new DBRef('namespace', oid, null)}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.equal("namespace", doc2.dbref.namespace); + test.deepEqual(doc2.dbref.oid.toHexString(), oid.toHexString()); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize partial DBRef'] = function(test) { + var id = new ObjectID(); + var doc = {'name':'something', 'user':{'$ref':'username', '$id': id}}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.equal('something', doc2.name); + test.equal('username', doc2.user.namespace); + test.equal(id.toString(), doc2.user.oid.toString()); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize simple Int'] = function(test) { + var doc = {doc:2147483648}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.doc, doc2.doc) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Long Integer'] = function(test) { + var doc = {doc: Long.fromNumber(9223372036854775807)}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.doc, deserialized_data.doc); + + doc = {doc: Long.fromNumber(-9223372036854775)}; + serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.doc, deserialized_data.doc); + + doc = {doc: Long.fromNumber(-9223372036854775809)}; + serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.doc, deserialized_data.doc); + test.done(); +} + +/** + * @ignore + */ +exports['Should Deserialize Large Integers as Number not Long'] = function(test) { + function roundTrip(val) { + var doc = {doc: val}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.doc, deserialized_data.doc); + }; + + roundTrip(Math.pow(2,52)); + roundTrip(Math.pow(2,53) - 1); + roundTrip(Math.pow(2,53)); + roundTrip(-Math.pow(2,52)); + roundTrip(-Math.pow(2,53) + 1); + roundTrip(-Math.pow(2,53)); + roundTrip(Math.pow(2,65)); // Too big for Long. + roundTrip(-Math.pow(2,65)); + roundTrip(9223372036854775807); + roundTrip(1234567890123456800); // Bigger than 2^53, stays a double. + roundTrip(-1234567890123456800); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Long Integer and Timestamp as different types'] = function(test) { + var long = Long.fromNumber(9223372036854775807); + var timestamp = Timestamp.fromNumber(9223372036854775807); + test.ok(long instanceof Long); + test.ok(!(long instanceof Timestamp)); + test.ok(timestamp instanceof Timestamp); + test.ok(!(timestamp instanceof Long)); + + var test_int = {doc: long, doc2: timestamp}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(test_int, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(test_int)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(test_int, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(test_int.doc, deserialized_data.doc); + test.done(); +} + +/** + * @ignore + */ +exports['Should Always put the id as the first item in a hash'] = function(test) { + var hash = {doc: {not_id:1, '_id':2}}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(hash, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(hash)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(hash, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + var keys = []; + + for(var name in deserialized_data.doc) { + keys.push(name); + } + + test.deepEqual(['not_id', '_id'], keys); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize a User defined Binary object'] = function(test) { + var bin = new Binary(); + bin.sub_type = BSON.BSON_BINARY_SUBTYPE_USER_DEFINED; + var string = 'binstring'; + for(var index = 0; index < string.length; index++) { + bin.put(string.charAt(index)); + } + + var doc = {doc: bin}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + + test.deepEqual(deserialized_data.doc.sub_type, BSON.BSON_BINARY_SUBTYPE_USER_DEFINED); + test.deepEqual(doc.doc.value(), deserialized_data.doc.value()); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correclty Serialize and Deserialize a Code object'] = function(test) { + var doc = {'doc': {'doc2': new Code('this.a > i', {i:1})}}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.doc.doc2.code, deserialized_data.doc.doc2.code); + test.deepEqual(doc.doc.doc2.scope.i, deserialized_data.doc.doc2.scope.i); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly serialize and deserialize and embedded array'] = function(test) { + var doc = {'a':0, + 'b':['tmp1', 'tmp2', 'tmp3', 'tmp4', 'tmp5', 'tmp6', 'tmp7', 'tmp8', 'tmp9', 'tmp10', 'tmp11', 'tmp12', 'tmp13', 'tmp14', 'tmp15', 'tmp16'] + }; + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.a, deserialized_data.a); + test.deepEqual(doc.b, deserialized_data.b); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize UTF8'] = function(test) { + // Serialize utf8 + var doc = { "name" : "本荘由利地域に洪水警報", "name1" : "öüóőúéáűíÖÜÓŐÚÉÁŰÍ", "name2" : "abcdedede"}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc, deserialized_data); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize query object'] = function(test) { + var doc = { count: 'remove_with_no_callback_bug_test', query: {}, fields: null}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc, deserialized_data); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize empty query object'] = function(test) { + var doc = {}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc, deserialized_data); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize array based doc'] = function(test) { + var doc = { b: [ 1, 2, 3 ], _id: new ObjectID() }; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.b, deserialized_data.b) + test.deepEqual(doc, deserialized_data); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Symbol'] = function(test) { + if(Symbol != null) { + var doc = { b: [ new Symbol('test') ]}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc.b, deserialized_data.b) + test.deepEqual(doc, deserialized_data); + test.ok(deserialized_data.b[0] instanceof Symbol); + } + + test.done(); +} + +/** + * @ignore + */ +exports['Should handle Deeply nested document'] = function(test) { + var doc = {a:{b:{c:{d:2}}}}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var deserialized_data = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc, deserialized_data); + test.done(); +} + +/** + * @ignore + */ +exports['Should handle complicated all typed object'] = function(test) { + // First doc + var date = new Date(); + var oid = new ObjectID(); + var string = 'binstring' + var bin = new Binary() + for(var index = 0; index < string.length; index++) { + bin.put(string.charAt(index)) + } + + var doc = { + 'string': 'hello', + 'array': [1,2,3], + 'hash': {'a':1, 'b':2}, + 'date': date, + 'oid': oid, + 'binary': bin, + 'int': 42, + 'float': 33.3333, + 'regexp': /regexp/, + 'boolean': true, + 'long': date.getTime(), + 'where': new Code('this.a > i', {i:1}), + 'dbref': new DBRef('namespace', oid, 'integration_tests_') + } + + // Second doc + var oid = new ObjectID.createFromHexString(oid.toHexString()); + var string = 'binstring' + var bin = new Binary() + for(var index = 0; index < string.length; index++) { + bin.put(string.charAt(index)) + } + + var doc2 = { + 'string': 'hello', + 'array': [1,2,3], + 'hash': {'a':1, 'b':2}, + 'date': date, + 'oid': oid, + 'binary': bin, + 'int': 42, + 'float': 33.3333, + 'regexp': /regexp/, + 'boolean': true, + 'long': date.getTime(), + 'where': new Code('this.a > i', {i:1}), + 'dbref': new DBRef('namespace', oid, 'integration_tests_') + } + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var serialized_data2 = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc2, false, true); + + for(var i = 0; i < serialized_data2.length; i++) { + require('assert').equal(serialized_data2[i], serialized_data[i]) + } + + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize Complex Nested Object'] = function(test) { + var doc = { email: 'email@email.com', + encrypted_password: 'password', + friends: [ '4db96b973d01205364000006', + '4dc77b24c5ba38be14000002' ], + location: [ 72.4930088, 23.0431957 ], + name: 'Amit Kumar', + password_salt: 'salty', + profile_fields: [], + username: 'amit', + _id: new ObjectID() } + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = doc; + doc2._id = ObjectID.createFromHexString(doc2._id.toHexString()); + var serialized_data2 = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc2, false, true); + + for(var i = 0; i < serialized_data2.length; i++) { + require('assert').equal(serialized_data2[i], serialized_data[i]) + } + + test.done(); +} + +/** + * @ignore + */ +exports['Should correctly massive doc'] = function(test) { + var oid1 = new ObjectID(); + var oid2 = new ObjectID(); + + // JS doc + var doc = { dbref2: new DBRef('namespace', oid1, 'integration_tests_'), + _id: oid2 }; + + var doc2 = { dbref2: new DBRef('namespace', ObjectID.createFromHexString(oid1.toHexString()), 'integration_tests_'), + _id: new ObjectID.createFromHexString(oid2.toHexString()) }; + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var serialized_data2 = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc2, false, true); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize/Deserialize regexp object'] = function(test) { + var doc = {'b':/foobaré/}; + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var serialized_data2 = new BSONDE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + for(var i = 0; i < serialized_data2.length; i++) { + require('assert').equal(serialized_data2[i], serialized_data[i]) + } + + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize/Deserialize complicated object'] = function(test) { + var doc = {a:{b:{c:[new ObjectID(), new ObjectID()]}}, d:{f:1332.3323}}; + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + + test.deepEqual(doc, doc2) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize/Deserialize nested object'] = function(test) { + var doc = { "_id" : { "date" : new Date(), "gid" : "6f35f74d2bea814e21000000" }, + "value" : { + "b" : { "countries" : { "--" : 386 }, "total" : 1599 }, + "bc" : { "countries" : { "--" : 3 }, "total" : 10 }, + "gp" : { "countries" : { "--" : 2 }, "total" : 13 }, + "mgc" : { "countries" : { "--" : 2 }, "total" : 14 } + } + } + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + + test.deepEqual(doc, doc2) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize/Deserialize nested object with even more nesting'] = function(test) { + var doc = { "_id" : { "date" : {a:1, b:2, c:new Date()}, "gid" : "6f35f74d2bea814e21000000" }, + "value" : { + "b" : { "countries" : { "--" : 386 }, "total" : 1599 }, + "bc" : { "countries" : { "--" : 3 }, "total" : 10 }, + "gp" : { "countries" : { "--" : 2 }, "total" : 13 }, + "mgc" : { "countries" : { "--" : 2 }, "total" : 14 } + } + } + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual(doc, doc2) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize empty name object'] = function(test) { + var doc = {'':'test', + 'bbbb':1}; + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.equal(doc2[''], 'test'); + test.equal(doc2['bbbb'], 1); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly handle Forced Doubles to ensure we allocate enough space for cap collections'] = function(test) { + if(Double != null) { + var doubleValue = new Double(100); + var doc = {value:doubleValue}; + + // Serialize + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + + var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + test.deepEqual({value:100}, doc2); + } + + test.done(); +} + +/** + * @ignore + */ +exports['Should deserialize correctly'] = function(test) { + var doc = { + "_id" : new ObjectID("4e886e687ff7ef5e00000162"), + "str" : "foreign", + "type" : 2, + "timestamp" : ISODate("2011-10-02T14:00:08.383Z"), + "links" : [ + "http://www.reddit.com/r/worldnews/comments/kybm0/uk_home_secretary_calls_for_the_scrapping_of_the/" + ] + } + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + + test.deepEqual(doc, doc2) + test.done(); +} + +/** + * @ignore + */ +exports['Should correctly serialize and deserialize MinKey and MaxKey values'] = function(test) { + var doc = { + _id : new ObjectID("4e886e687ff7ef5e00000162"), + minKey : new MinKey(), + maxKey : new MaxKey() + } + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + + test.deepEqual(doc, doc2) + test.ok(doc2.minKey instanceof MinKey); + test.ok(doc2.maxKey instanceof MaxKey); + test.done(); +} + +/** + * @ignore + */ +exports['Should correctly serialize Double value'] = function(test) { + var doc = { + value : new Double(34343.2222) + } + + var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); + var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); + new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); + assertBuffersEqual(test, serialized_data, serialized_data2, 0); + var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data); + + test.ok(doc.value.valueOf(), doc2.value); + test.ok(doc.value.value, doc2.value); + test.done(); +} + +/** + * @ignore + */ +exports['ObjectID should correctly create objects'] = function(test) { + try { + var object1 = ObjectID.createFromHexString('000000000000000000000001') + var object2 = ObjectID.createFromHexString('00000000000000000000001') + test.ok(false); + } catch(err) { + test.ok(err != null); + } + + test.done(); +} + +/** + * @ignore + */ +exports['ObjectID should correctly retrieve timestamp'] = function(test) { + var testDate = new Date(); + var object1 = new ObjectID(); + test.equal(Math.floor(testDate.getTime()/1000), Math.floor(object1.getTimestamp().getTime()/1000)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly throw error on bsonparser errors'] = function(test) { + var data = new Buffer(3); + var parser = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); + + // Catch to small buffer error + try { + parser.deserialize(data); + test.ok(false); + } catch(err) {} + + data = new Buffer(5); + data[0] = 0xff; + data[1] = 0xff; + // Catch illegal size + try { + parser.deserialize(data); + test.ok(false); + } catch(err) {} + + // Finish up + test.done(); +} + +/** + * A simple example showing the usage of BSON.calculateObjectSize function returning the number of BSON bytes a javascript object needs. + * + * @_class bson + * @_function BSON.calculateObjectSize + * @ignore + */ +exports['Should correctly calculate the size of a given javascript object'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){}} + // Calculate the size of the object without serializing the function + var size = BSON.calculateObjectSize(doc, false); + test.equal(12, size); + // Calculate the size of the object serializing the function + size = BSON.calculateObjectSize(doc, true); + // Validate the correctness + test.equal(36, size); + test.done(); +} + +/** + * A simple example showing the usage of BSON.calculateObjectSize function returning the number of BSON bytes a javascript object needs. + * + * @_class bson + * @_function calculateObjectSize + * @ignore + */ +exports['Should correctly calculate the size of a given javascript object using instance method'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){}} + // Create a BSON parser instance + var bson = new BSON(); + // Calculate the size of the object without serializing the function + var size = bson.calculateObjectSize(doc, false); + test.equal(12, size); + // Calculate the size of the object serializing the function + size = bson.calculateObjectSize(doc, true); + // Validate the correctness + test.equal(36, size); + test.done(); +} + +/** + * A simple example showing the usage of BSON.serializeWithBufferAndIndex function. + * + * @_class bson + * @_function BSON.serializeWithBufferAndIndex + * @ignore + */ +exports['Should correctly serializeWithBufferAndIndex a given javascript object'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){}} + // Calculate the size of the document, no function serialization + var size = BSON.calculateObjectSize(doc, false); + // Allocate a buffer + var buffer = new Buffer(size); + // Serialize the object to the buffer, checking keys and not serializing functions + var index = BSON.serializeWithBufferAndIndex(doc, true, buffer, 0, false); + // Validate the correctness + test.equal(12, size); + test.equal(11, index); + + // Serialize with functions + // Calculate the size of the document, no function serialization + var size = BSON.calculateObjectSize(doc, true); + // Allocate a buffer + var buffer = new Buffer(size); + // Serialize the object to the buffer, checking keys and not serializing functions + var index = BSON.serializeWithBufferAndIndex(doc, true, buffer, 0, true); + // Validate the correctness + test.equal(36, size); + test.equal(35, index); + test.done(); +} + +/** + * A simple example showing the usage of BSON.serializeWithBufferAndIndex function. + * + * @_class bson + * @_function serializeWithBufferAndIndex + * @ignore + */ +exports['Should correctly serializeWithBufferAndIndex a given javascript object using a BSON instance'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){}} + // Create a BSON parser instance + var bson = new BSON(); + // Calculate the size of the document, no function serialization + var size = bson.calculateObjectSize(doc, false); + // Allocate a buffer + var buffer = new Buffer(size); + // Serialize the object to the buffer, checking keys and not serializing functions + var index = bson.serializeWithBufferAndIndex(doc, true, buffer, 0, false); + // Validate the correctness + test.equal(12, size); + test.equal(11, index); + + // Serialize with functions + // Calculate the size of the document, no function serialization + var size = bson.calculateObjectSize(doc, true); + // Allocate a buffer + var buffer = new Buffer(size); + // Serialize the object to the buffer, checking keys and not serializing functions + var index = bson.serializeWithBufferAndIndex(doc, true, buffer, 0, true); + // Validate the correctness + test.equal(36, size); + test.equal(35, index); + test.done(); +} + +/** + * A simple example showing the usage of BSON.serialize function returning serialized BSON Buffer object. + * + * @_class bson + * @_function BSON.serialize + * @ignore + */ +exports['Should correctly serialize a given javascript object'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){}} + // Serialize the object to a buffer, checking keys and not serializing functions + var buffer = BSON.serialize(doc, true, true, false); + // Validate the correctness + test.equal(12, buffer.length); + + // Serialize the object to a buffer, checking keys and serializing functions + var buffer = BSON.serialize(doc, true, true, true); + // Validate the correctness + test.equal(36, buffer.length); + test.done(); +} + +/** + * A simple example showing the usage of BSON.serialize function returning serialized BSON Buffer object. + * + * @_class bson + * @_function serialize + * @ignore + */ +exports['Should correctly serialize a given javascript object using a bson instance'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){}} + // Create a BSON parser instance + var bson = new BSON(); + // Serialize the object to a buffer, checking keys and not serializing functions + var buffer = bson.serialize(doc, true, true, false); + // Validate the correctness + test.equal(12, buffer.length); + + // Serialize the object to a buffer, checking keys and serializing functions + var buffer = bson.serialize(doc, true, true, true); + // Validate the correctness + test.equal(36, buffer.length); + test.done(); +} + +/** + * A simple example showing the usage of BSON.deserialize function returning a deserialized Javascript function. + * + * @_class bson + * @_function BSON.deserialize + * @ignore + */ + exports['Should correctly deserialize a buffer using the BSON class level parser'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){ console.log('hello world'); }} + // Serialize the object to a buffer, checking keys and serializing functions + var buffer = BSON.serialize(doc, true, true, true); + // Validate the correctness + test.equal(65, buffer.length); + + // Deserialize the object with no eval for the functions + var deserializedDoc = BSON.deserialize(buffer); + // Validate the correctness + test.equal('object', typeof deserializedDoc.func); + test.equal(1, deserializedDoc.a); + + // Deserialize the object with eval for the functions caching the functions + deserializedDoc = BSON.deserialize(buffer, {evalFunctions:true, cacheFunctions:true}); + // Validate the correctness + test.equal('function', typeof deserializedDoc.func); + test.equal(1, deserializedDoc.a); + test.done(); +} + +/** + * A simple example showing the usage of BSON instance deserialize function returning a deserialized Javascript function. + * + * @_class bson + * @_function deserialize + * @ignore + */ +exports['Should correctly deserialize a buffer using the BSON instance parser'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){ console.log('hello world'); }} + // Create a BSON parser instance + var bson = new BSON(); + // Serialize the object to a buffer, checking keys and serializing functions + var buffer = bson.serialize(doc, true, true, true); + // Validate the correctness + test.equal(65, buffer.length); + + // Deserialize the object with no eval for the functions + var deserializedDoc = bson.deserialize(buffer); + // Validate the correctness + test.equal('object', typeof deserializedDoc.func); + test.equal(1, deserializedDoc.a); + + // Deserialize the object with eval for the functions caching the functions + deserializedDoc = bson.deserialize(buffer, {evalFunctions:true, cacheFunctions:true}); + // Validate the correctness + test.equal('function', typeof deserializedDoc.func); + test.equal(1, deserializedDoc.a); + test.done(); +} + +/** + * A simple example showing the usage of BSON.deserializeStream function returning deserialized Javascript objects. + * + * @_class bson + * @_function BSON.deserializeStream + * @ignore + */ +exports['Should correctly deserializeStream a buffer object'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){ console.log('hello world'); }} + // Serialize the object to a buffer, checking keys and serializing functions + var buffer = BSON.serialize(doc, true, true, true); + // Validate the correctness + test.equal(65, buffer.length); + + // The array holding the number of retuned documents + var documents = new Array(1); + // Deserialize the object with no eval for the functions + var index = BSON.deserializeStream(buffer, 0, 1, documents, 0); + // Validate the correctness + test.equal(65, index); + test.equal(1, documents.length); + test.equal(1, documents[0].a); + test.equal('object', typeof documents[0].func); + + // Deserialize the object with eval for the functions caching the functions + // The array holding the number of retuned documents + var documents = new Array(1); + // Deserialize the object with no eval for the functions + var index = BSON.deserializeStream(buffer, 0, 1, documents, 0, {evalFunctions:true, cacheFunctions:true}); + // Validate the correctness + test.equal(65, index); + test.equal(1, documents.length); + test.equal(1, documents[0].a); + test.equal('function', typeof documents[0].func); + test.done(); +} + +/** + * A simple example showing the usage of BSON instance deserializeStream function returning deserialized Javascript objects. + * + * @_class bson + * @_function deserializeStream + * @ignore + */ +exports['Should correctly deserializeStream a buffer object'] = function(test) { + // Create a simple object + var doc = {a: 1, func:function(){ console.log('hello world'); }} + // Create a BSON parser instance + var bson = new BSON(); + // Serialize the object to a buffer, checking keys and serializing functions + var buffer = bson.serialize(doc, true, true, true); + // Validate the correctness + test.equal(65, buffer.length); + + // The array holding the number of retuned documents + var documents = new Array(1); + // Deserialize the object with no eval for the functions + var index = bson.deserializeStream(buffer, 0, 1, documents, 0); + // Validate the correctness + test.equal(65, index); + test.equal(1, documents.length); + test.equal(1, documents[0].a); + test.equal('object', typeof documents[0].func); + + // Deserialize the object with eval for the functions caching the functions + // The array holding the number of retuned documents + var documents = new Array(1); + // Deserialize the object with no eval for the functions + var index = bson.deserializeStream(buffer, 0, 1, documents, 0, {evalFunctions:true, cacheFunctions:true}); + // Validate the correctness + test.equal(65, index); + test.equal(1, documents.length); + test.equal(1, documents[0].a); + test.equal('function', typeof documents[0].func); + test.done(); +} + +/** + * @ignore + */ +// 'Should Correctly Function' = function(test) { +// var doc = {b:1, func:function() { +// this.b = 2; +// }}; +// +// var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc, false, true); +// +// debug("----------------------------------------------------------------------") +// debug(inspect(serialized_data)) +// +// // var serialized_data2 = new Buffer(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).calculateObjectSize(doc, false, true)); +// // new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serializeWithBufferAndIndex(doc, false, serialized_data2, 0); +// // assertBuffersEqual(test, serialized_data, serialized_data2, 0); +// var COUNT = 100000; +// +// // var b = null; +// // eval("b = function(x) { return x+x; }"); +// // var b = new Function("x", "return x+x;"); +// +// console.log(COUNT + "x (objectBSON = BSON.serialize(object))") +// start = new Date +// +// for (i=COUNT; --i>=0; ) { +// var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data, {evalFunctions: true, cacheFunctions:true}); +// } +// +// end = new Date +// console.log("time = ", end - start, "ms -", COUNT * 1000 / (end - start), " ops/sec") +// +// // debug(inspect(new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).functionCache)) +// // +// // var doc2 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data, {evalFunctions: true, cacheFunctions:true}); +// // // test.deepEqual(doc, doc2) +// // // +// // debug(inspect(doc2)) +// // doc2.func() +// // debug(inspect(doc2)) +// // +// // var serialized_data = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).serialize(doc2, false, true); +// // var doc3 = new BSONSE.BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]).deserialize(serialized_data, {evalFunctions: true, cacheFunctions:true}); +// // +// // debug("-----------------------------------------------") +// // debug(inspect(doc3)) +// +// // var key = "0" +// // for(var i = 1; i < 10000; i++) { +// // key = key + " " + i +// // } +// +// test.done(); +// +// +// // var car = { +// // model : "Volvo", +// // country : "Sweden", +// // +// // isSwedish : function() { +// // return this.country == "Sweden"; +// // } +// // } +// +// }, + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.noGlobalsLeaked = function(test) { + var leaks = gleak.detectNew(); + test.equal(0, leaks.length, "global var leak detected: " + leaks.join(', ')); + test.done(); +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/test/node/bson_typed_array_test.js b/node_modules/mongodb/node_modules/bson/test/node/bson_typed_array_test.js new file mode 100644 index 0000000..cde83f8 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/node/bson_typed_array_test.js @@ -0,0 +1,392 @@ +var mongodb = require('../../lib/bson').pure(); + +var testCase = require('nodeunit').testCase, + mongoO = require('../../lib/bson').pure(), + debug = require('util').debug, + inspect = require('util').inspect, + Buffer = require('buffer').Buffer, + gleak = require('../../tools/gleak'), + fs = require('fs'), + BSON = mongoO.BSON, + Code = mongoO.Code, + Binary = mongoO.Binary, + Timestamp = mongoO.Timestamp, + Long = mongoO.Long, + MongoReply = mongoO.MongoReply, + ObjectID = mongoO.ObjectID, + Symbol = mongoO.Symbol, + DBRef = mongoO.DBRef, + Double = mongoO.Double, + MinKey = mongoO.MinKey, + MaxKey = mongoO.MaxKey, + BinaryParser = mongoO.BinaryParser, + utils = require('./tools/utils'); + +var BSONSE = mongodb, + BSONDE = mongodb; + +// for tests +BSONDE.BSON_BINARY_SUBTYPE_DEFAULT = 0; +BSONDE.BSON_BINARY_SUBTYPE_FUNCTION = 1; +BSONDE.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +BSONDE.BSON_BINARY_SUBTYPE_UUID = 3; +BSONDE.BSON_BINARY_SUBTYPE_MD5 = 4; +BSONDE.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; + +BSONSE.BSON_BINARY_SUBTYPE_DEFAULT = 0; +BSONSE.BSON_BINARY_SUBTYPE_FUNCTION = 1; +BSONSE.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; +BSONSE.BSON_BINARY_SUBTYPE_UUID = 3; +BSONSE.BSON_BINARY_SUBTYPE_MD5 = 4; +BSONSE.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; + +var hexStringToBinary = function(string) { + var numberofValues = string.length / 2; + var array = ""; + + for(var i = 0; i < numberofValues; i++) { + array += String.fromCharCode(parseInt(string[i*2] + string[i*2 + 1], 16)); + } + return array; +} + +var assertBuffersEqual = function(test, buffer1, buffer2) { + if(buffer1.length != buffer2.length) test.fail("Buffers do not have the same length", buffer1, buffer2); + + for(var i = 0; i < buffer1.length; i++) { + test.equal(buffer1[i], buffer2[i]); + } +} + +/** + * Module for parsing an ISO 8601 formatted string into a Date object. + */ +var ISODate = function (string) { + var match; + + if (typeof string.getTime === "function") + return string; + else if (match = string.match(/^(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|((\+|-)(\d{2}):(\d{2}))))?)?)?$/)) { + var date = new Date(); + date.setUTCFullYear(Number(match[1])); + date.setUTCMonth(Number(match[3]) - 1 || 0); + date.setUTCDate(Number(match[5]) || 0); + date.setUTCHours(Number(match[7]) || 0); + date.setUTCMinutes(Number(match[8]) || 0); + date.setUTCSeconds(Number(match[10]) || 0); + date.setUTCMilliseconds(Number("." + match[12]) * 1000 || 0); + + if (match[13] && match[13] !== "Z") { + var h = Number(match[16]) || 0, + m = Number(match[17]) || 0; + + h *= 3600000; + m *= 60000; + + var offset = h + m; + if (match[15] == "+") + offset = -offset; + + date = new Date(date.valueOf() + offset); + } + + return date; + } else + throw new Error("Invalid ISO 8601 date given.", __filename); +}; + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.setUp = function(callback) { + callback(); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.tearDown = function(callback) { + callback(); +} + +/** + * @ignore + */ +exports.shouldCorrectlyDeserializeUsingTypedArray = function(test) { + if(typeof ArrayBuffer == 'undefined') { + test.done(); + return; + } + + var motherOfAllDocuments = { + 'string': '客家话', + 'array': [1,2,3], + 'hash': {'a':1, 'b':2}, + 'date': new Date(), + 'oid': new ObjectID(), + 'binary': new Binary(new Buffer("hello")), + 'int': 42, + 'float': 33.3333, + 'regexp': /regexp/, + 'boolean': true, + 'long': Long.fromNumber(100), + 'where': new Code('this.a > i', {i:1}), + 'dbref': new DBRef('namespace', new ObjectID(), 'integration_tests_'), + 'minkey': new MinKey(), + 'maxkey': new MaxKey() + } + + // Let's serialize it + var data = BSONSE.BSON.serialize(motherOfAllDocuments, true, true, false); + // Build a typed array + var arr = new Uint8Array(new ArrayBuffer(data.length)); + // Iterate over all the fields and copy + for(var i = 0; i < data.length; i++) { + arr[i] = data[i] + } + + // Deserialize the object + var object = BSONDE.BSON.deserialize(arr); + // Asserts + test.equal(motherOfAllDocuments.string, object.string); + test.deepEqual(motherOfAllDocuments.array, object.array); + test.deepEqual(motherOfAllDocuments.date, object.date); + test.deepEqual(motherOfAllDocuments.oid.toHexString(), object.oid.toHexString()); + test.deepEqual(motherOfAllDocuments.binary.length(), object.binary.length()); + // Assert the values of the binary + for(var i = 0; i < motherOfAllDocuments.binary.length(); i++) { + test.equal(motherOfAllDocuments.binary.value[i], object.binary[i]); + } + test.deepEqual(motherOfAllDocuments.int, object.int); + test.deepEqual(motherOfAllDocuments.float, object.float); + test.deepEqual(motherOfAllDocuments.regexp, object.regexp); + test.deepEqual(motherOfAllDocuments.boolean, object.boolean); + test.deepEqual(motherOfAllDocuments.long.toNumber(), object.long); + test.deepEqual(motherOfAllDocuments.where, object.where); + test.deepEqual(motherOfAllDocuments.dbref.oid.toHexString(), object.dbref.oid.toHexString()); + test.deepEqual(motherOfAllDocuments.dbref.namespace, object.dbref.namespace); + test.deepEqual(motherOfAllDocuments.dbref.db, object.dbref.db); + test.deepEqual(motherOfAllDocuments.minkey, object.minkey); + test.deepEqual(motherOfAllDocuments.maxkey, object.maxkey); + test.done(); +} + +/** + * @ignore + */ +exports.shouldCorrectlySerializeUsingTypedArray = function(test) { + if(typeof ArrayBuffer == 'undefined') { + test.done(); + return; + } + + var motherOfAllDocuments = { + 'string': 'hello', + 'array': [1,2,3], + 'hash': {'a':1, 'b':2}, + 'date': new Date(), + 'oid': new ObjectID(), + 'binary': new Binary(new Buffer("hello")), + 'int': 42, + 'float': 33.3333, + 'regexp': /regexp/, + 'boolean': true, + 'long': Long.fromNumber(100), + 'where': new Code('this.a > i', {i:1}), + 'dbref': new DBRef('namespace', new ObjectID(), 'integration_tests_'), + 'minkey': new MinKey(), + 'maxkey': new MaxKey() + } + + // Let's serialize it + var data = BSONSE.BSON.serialize(motherOfAllDocuments, true, false, false); + // And deserialize it again + var object = BSONSE.BSON.deserialize(data); + // Asserts + test.equal(motherOfAllDocuments.string, object.string); + test.deepEqual(motherOfAllDocuments.array, object.array); + test.deepEqual(motherOfAllDocuments.date, object.date); + test.deepEqual(motherOfAllDocuments.oid.toHexString(), object.oid.toHexString()); + test.deepEqual(motherOfAllDocuments.binary.length(), object.binary.length()); + // Assert the values of the binary + for(var i = 0; i < motherOfAllDocuments.binary.length(); i++) { + test.equal(motherOfAllDocuments.binary.value[i], object.binary[i]); + } + test.deepEqual(motherOfAllDocuments.int, object.int); + test.deepEqual(motherOfAllDocuments.float, object.float); + test.deepEqual(motherOfAllDocuments.regexp, object.regexp); + test.deepEqual(motherOfAllDocuments.boolean, object.boolean); + test.deepEqual(motherOfAllDocuments.long.toNumber(), object.long); + test.deepEqual(motherOfAllDocuments.where, object.where); + test.deepEqual(motherOfAllDocuments.dbref.oid.toHexString(), object.dbref.oid.toHexString()); + test.deepEqual(motherOfAllDocuments.dbref.namespace, object.dbref.namespace); + test.deepEqual(motherOfAllDocuments.dbref.db, object.dbref.db); + test.deepEqual(motherOfAllDocuments.minkey, object.minkey); + test.deepEqual(motherOfAllDocuments.maxkey, object.maxkey); + test.done(); +} + +/** + * @ignore + */ +exports['exercise all the binary object constructor methods'] = function (test) { + if(typeof ArrayBuffer == 'undefined') { + test.done(); + return; + } + + // Construct using array + var string = 'hello world'; + // String to array + var array = utils.stringToArrayBuffer(string); + + // Binary from array buffer + var binary = new Binary(utils.stringToArrayBuffer(string)); + test.ok(string.length, binary.buffer.length); + test.ok(utils.assertArrayEqual(array, binary.buffer)); + + // Construct using number of chars + binary = new Binary(5); + test.ok(5, binary.buffer.length); + + // Construct using an Array + var binary = new Binary(utils.stringToArray(string)); + test.ok(string.length, binary.buffer.length); + test.ok(utils.assertArrayEqual(array, binary.buffer)); + + // Construct using a string + var binary = new Binary(string); + test.ok(string.length, binary.buffer.length); + test.ok(utils.assertArrayEqual(array, binary.buffer)); + test.done(); +}; + +/** + * @ignore + */ +exports['exercise the put binary object method for an instance when using Uint8Array'] = function (test) { + if(typeof ArrayBuffer == 'undefined') { + test.done(); + return; + } + + // Construct using array + var string = 'hello world'; + // String to array + var array = utils.stringToArrayBuffer(string + 'a'); + + // Binary from array buffer + var binary = new Binary(utils.stringToArrayBuffer(string)); + test.ok(string.length, binary.buffer.length); + + // Write a byte to the array + binary.put('a') + + // Verify that the data was writtencorrectly + test.equal(string.length + 1, binary.position); + test.ok(utils.assertArrayEqual(array, binary.value(true))); + test.equal('hello worlda', binary.value()); + + // Exercise a binary with lots of space in the buffer + var binary = new Binary(); + test.ok(Binary.BUFFER_SIZE, binary.buffer.length); + + // Write a byte to the array + binary.put('a') + + // Verify that the data was writtencorrectly + test.equal(1, binary.position); + test.ok(utils.assertArrayEqual(['a'.charCodeAt(0)], binary.value(true))); + test.equal('a', binary.value()); + test.done(); +}, + +/** + * @ignore + */ +exports['exercise the write binary object method for an instance when using Uint8Array'] = function (test) { + if(typeof ArrayBuffer == 'undefined') { + test.done(); + return; + } + + // Construct using array + var string = 'hello world'; + // Array + var writeArrayBuffer = new Uint8Array(new ArrayBuffer(1)); + writeArrayBuffer[0] = 'a'.charCodeAt(0); + var arrayBuffer = ['a'.charCodeAt(0)]; + + // Binary from array buffer + var binary = new Binary(utils.stringToArrayBuffer(string)); + test.ok(string.length, binary.buffer.length); + + // Write a string starting at end of buffer + binary.write('a'); + test.equal('hello worlda', binary.value()); + // Write a string starting at index 0 + binary.write('a', 0); + test.equal('aello worlda', binary.value()); + // Write a arraybuffer starting at end of buffer + binary.write(writeArrayBuffer); + test.equal('aello worldaa', binary.value()); + // Write a arraybuffer starting at position 5 + binary.write(writeArrayBuffer, 5); + test.equal('aelloaworldaa', binary.value()); + // Write a array starting at end of buffer + binary.write(arrayBuffer); + test.equal('aelloaworldaaa', binary.value()); + // Write a array starting at position 6 + binary.write(arrayBuffer, 6); + test.equal('aelloaaorldaaa', binary.value()); + test.done(); +}, + +/** + * @ignore + */ +exports['exercise the read binary object method for an instance when using Uint8Array'] = function (test) { + if(typeof ArrayBuffer == 'undefined') { + test.done(); + return; + } + + // Construct using array + var string = 'hello world'; + var array = utils.stringToArrayBuffer(string); + + // Binary from array buffer + var binary = new Binary(utils.stringToArrayBuffer(string)); + test.ok(string.length, binary.buffer.length); + + // Read the first 2 bytes + var data = binary.read(0, 2); + test.ok(utils.assertArrayEqual(utils.stringToArrayBuffer('he'), data)); + + // Read the entire field + var data = binary.read(0); + test.ok(utils.assertArrayEqual(utils.stringToArrayBuffer(string), data)); + + // Read 3 bytes + var data = binary.read(6, 5); + test.ok(utils.assertArrayEqual(utils.stringToArrayBuffer('world'), data)); + test.done(); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.noGlobalsLeaked = function(test) { + var leaks = gleak.detectNew(); + test.equal(0, leaks.length, "global var leak detected: " + leaks.join(', ')); + test.done(); +} \ No newline at end of file diff --git a/node_modules/mongodb/node_modules/bson/test/node/data/test_gs_weird_bug.png b/node_modules/mongodb/node_modules/bson/test/node/data/test_gs_weird_bug.png new file mode 100644 index 0000000..1554dc3 Binary files /dev/null and b/node_modules/mongodb/node_modules/bson/test/node/data/test_gs_weird_bug.png differ diff --git a/node_modules/mongodb/node_modules/bson/test/node/test_full_bson.js b/node_modules/mongodb/node_modules/bson/test/node/test_full_bson.js new file mode 100644 index 0000000..fbeeb66 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/node/test_full_bson.js @@ -0,0 +1,295 @@ +var sys = require('util'), + fs = require('fs'), + Buffer = require('buffer').Buffer, + BSON = require('../../ext/bson').BSON, + Buffer = require('buffer').Buffer, + BSONJS = require('../../lib/bson/bson').BSON, + BinaryParser = require('../../lib/bson/binary_parser').BinaryParser, + Long = require('../../lib/bson/long').Long, + ObjectID = require('../../lib/bson/bson').ObjectID, + Binary = require('../../lib/bson/bson').Binary, + Code = require('../../lib/bson/bson').Code, + DBRef = require('../../lib/bson/bson').DBRef, + Symbol = require('../../lib/bson/bson').Symbol, + Double = require('../../lib/bson/bson').Double, + MaxKey = require('../../lib/bson/bson').MaxKey, + MinKey = require('../../lib/bson/bson').MinKey, + Timestamp = require('../../lib/bson/bson').Timestamp, + assert = require('assert'); + +// Parsers +var bsonC = new BSON([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); +var bsonJS = new BSONJS([Long, ObjectID, Binary, Code, DBRef, Symbol, Double, Timestamp, MaxKey, MinKey]); + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.setUp = function(callback) { + callback(); +} + +/** + * Retrieve the server information for the current + * instance of the db client + * + * @ignore + */ +exports.tearDown = function(callback) { + callback(); +} + +/** + * @ignore + */ +exports['Should Correctly Deserialize object'] = function(test) { + var bytes = [95,0,0,0,2,110,115,0,42,0,0,0,105,110,116,101,103,114,97,116,105,111,110,95,116,101,115,116,115,95,46,116,101,115,116,95,105,110,100,101,120,95,105,110,102,111,114,109,97,116,105,111,110,0,8,117,110,105,113,117,101,0,0,3,107,101,121,0,12,0,0,0,16,97,0,1,0,0,0,0,2,110,97,109,101,0,4,0,0,0,97,95,49,0,0]; + var serialized_data = ''; + // Convert to chars + for(var i = 0; i < bytes.length; i++) { + serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); + } + + var object = bsonC.deserialize(serialized_data); + assert.equal("a_1", object.name); + assert.equal(false, object.unique); + assert.equal(1, object.key.a); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Deserialize object with all types'] = function(test) { + var bytes = [26,1,0,0,7,95,105,100,0,161,190,98,75,118,169,3,0,0,3,0,0,4,97,114,114,97,121,0,26,0,0,0,16,48,0,1,0,0,0,16,49,0,2,0,0,0,16,50,0,3,0,0,0,0,2,115,116,114,105,110,103,0,6,0,0,0,104,101,108,108,111,0,3,104,97,115,104,0,19,0,0,0,16,97,0,1,0,0,0,16,98,0,2,0,0,0,0,9,100,97,116,101,0,161,190,98,75,0,0,0,0,7,111,105,100,0,161,190,98,75,90,217,18,0,0,1,0,0,5,98,105,110,97,114,121,0,7,0,0,0,2,3,0,0,0,49,50,51,16,105,110,116,0,42,0,0,0,1,102,108,111,97,116,0,223,224,11,147,169,170,64,64,11,114,101,103,101,120,112,0,102,111,111,98,97,114,0,105,0,8,98,111,111,108,101,97,110,0,1,15,119,104,101,114,101,0,25,0,0,0,12,0,0,0,116,104,105,115,46,120,32,61,61,32,51,0,5,0,0,0,0,3,100,98,114,101,102,0,37,0,0,0,2,36,114,101,102,0,5,0,0,0,116,101,115,116,0,7,36,105,100,0,161,190,98,75,2,180,1,0,0,2,0,0,0,10,110,117,108,108,0,0]; + var serialized_data = ''; + // Convert to chars + for(var i = 0; i < bytes.length; i++) { + serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]); + } + + var object = bsonJS.deserialize(new Buffer(serialized_data, 'binary')); + assert.equal("hello", object.string); + assert.deepEqual([1, 2, 3], object.array); + assert.equal(1, object.hash.a); + assert.equal(2, object.hash.b); + assert.ok(object.date != null); + assert.ok(object.oid != null); + assert.ok(object.binary != null); + assert.equal(42, object.int); + assert.equal(33.3333, object.float); + assert.ok(object.regexp != null); + assert.equal(true, object.boolean); + assert.ok(object.where != null); + assert.ok(object.dbref != null); + assert.ok(object['null'] == null); + test.done(); +} + +/** + * @ignore + */ +exports['Should Serialize and Deserialize String'] = function(test) { + var test_string = {hello: 'world'} + var serialized_data = bsonC.serialize(test_string) + assert.deepEqual(test_string, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Integer'] = function(test) { + var test_number = {doc: 5} + var serialized_data = bsonC.serialize(test_number) + assert.deepEqual(test_number, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize null value'] = function(test) { + var test_null = {doc:null} + var serialized_data = bsonC.serialize(test_null) + var object = bsonC.deserialize(serialized_data); + assert.deepEqual(test_null, object); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize undefined value'] = function(test) { + var test_undefined = {doc:undefined} + var serialized_data = bsonC.serialize(test_undefined) + var object = bsonJS.deserialize(new Buffer(serialized_data, 'binary')); + assert.equal(null, object.doc) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Number'] = function(test) { + var test_number = {doc: 5.5} + var serialized_data = bsonC.serialize(test_number) + assert.deepEqual(test_number, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Integer'] = function(test) { + var test_int = {doc: 42} + var serialized_data = bsonC.serialize(test_int) + assert.deepEqual(test_int, bsonC.deserialize(serialized_data)); + + test_int = {doc: -5600} + serialized_data = bsonC.serialize(test_int) + assert.deepEqual(test_int, bsonC.deserialize(serialized_data)); + + test_int = {doc: 2147483647} + serialized_data = bsonC.serialize(test_int) + assert.deepEqual(test_int, bsonC.deserialize(serialized_data)); + + test_int = {doc: -2147483648} + serialized_data = bsonC.serialize(test_int) + assert.deepEqual(test_int, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Object'] = function(test) { + var doc = {doc: {age: 42, name: 'Spongebob', shoe_size: 9.5}} + var serialized_data = bsonC.serialize(doc) + assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Array'] = function(test) { + var doc = {doc: [1, 2, 'a', 'b']} + var serialized_data = bsonC.serialize(doc) + assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Array with added on functions'] = function(test) { + var doc = {doc: [1, 2, 'a', 'b']} + var serialized_data = bsonC.serialize(doc) + assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize A Boolean'] = function(test) { + var doc = {doc: true} + var serialized_data = bsonC.serialize(doc) + assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize a Date'] = function(test) { + var date = new Date() + //(2009, 11, 12, 12, 00, 30) + date.setUTCDate(12) + date.setUTCFullYear(2009) + date.setUTCMonth(11 - 1) + date.setUTCHours(12) + date.setUTCMinutes(0) + date.setUTCSeconds(30) + var doc = {doc: date} + var serialized_data = bsonC.serialize(doc) + assert.deepEqual(doc, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Oid'] = function(test) { + var doc = {doc: new ObjectID()} + var serialized_data = bsonC.serialize(doc) + assert.deepEqual(doc.doc.toHexString(), bsonC.deserialize(serialized_data).doc.toHexString()) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly encode Empty Hash'] = function(test) { + var test_code = {} + var serialized_data = bsonC.serialize(test_code) + assert.deepEqual(test_code, bsonC.deserialize(serialized_data)); + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Ordered Hash'] = function(test) { + var doc = {doc: {b:1, a:2, c:3, d:4}} + var serialized_data = bsonC.serialize(doc) + var decoded_hash = bsonC.deserialize(serialized_data).doc + var keys = [] + for(name in decoded_hash) keys.push(name) + assert.deepEqual(['b', 'a', 'c', 'd'], keys) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize Regular Expression'] = function(test) { + var doc = {doc: /foobar/mi} + var serialized_data = bsonC.serialize(doc) + var doc2 = bsonC.deserialize(serialized_data); + assert.equal(doc.doc.toString(), doc2.doc.toString()) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize a Binary object'] = function(test) { + var bin = new Binary() + var string = 'binstring' + for(var index = 0; index < string.length; index++) { + bin.put(string.charAt(index)) + } + var doc = {doc: bin} + var serialized_data = bsonC.serialize(doc) + var deserialized_data = bsonC.deserialize(serialized_data); + assert.equal(doc.doc.value(), deserialized_data.doc.value()) + test.done(); +} + +/** + * @ignore + */ +exports['Should Correctly Serialize and Deserialize a big Binary object'] = function(test) { + var data = fs.readFileSync("test/node/data/test_gs_weird_bug.png", 'binary'); + var bin = new Binary() + bin.write(data) + var doc = {doc: bin} + var serialized_data = bsonC.serialize(doc) + var deserialized_data = bsonC.deserialize(serialized_data); + assert.equal(doc.doc.value(), deserialized_data.doc.value()) + test.done(); +} diff --git a/node_modules/mongodb/node_modules/bson/test/node/tools/utils.js b/node_modules/mongodb/node_modules/bson/test/node/tools/utils.js new file mode 100644 index 0000000..9d7cbe7 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/test/node/tools/utils.js @@ -0,0 +1,80 @@ +exports.assertArrayEqual = function(array1, array2) { + if(array1.length != array2.length) return false; + for(var i = 0; i < array1.length; i++) { + if(array1[i] != array2[i]) return false; + } + + return true; +} + +// String to arraybuffer +exports.stringToArrayBuffer = function(string) { + var dataBuffer = new Uint8Array(new ArrayBuffer(string.length)); + // Return the strings + for(var i = 0; i < string.length; i++) { + dataBuffer[i] = string.charCodeAt(i); + } + // Return the data buffer + return dataBuffer; +} + +// String to arraybuffer +exports.stringToArray = function(string) { + var dataBuffer = new Array(string.length); + // Return the strings + for(var i = 0; i < string.length; i++) { + dataBuffer[i] = string.charCodeAt(i); + } + // Return the data buffer + return dataBuffer; +} + +exports.Utf8 = { + // public method for url encoding + encode : function (string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + + return utftext; + }, + + // public method for url decoding + decode : function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while ( i < utftext.length ) { + c = utftext.charCodeAt(i); + if(c < 128) { + string += String.fromCharCode(c); + i++; + } else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; + } +} diff --git a/node_modules/mongodb/node_modules/bson/tools/gleak.js b/node_modules/mongodb/node_modules/bson/tools/gleak.js new file mode 100644 index 0000000..1737a6d --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/tools/gleak.js @@ -0,0 +1,8 @@ + +var gleak = require('gleak')(); +gleak.ignore('AssertionError'); +gleak.ignore('testFullSpec_param_found'); +gleak.ignore('events'); +gleak.ignore('Uint8Array'); + +module.exports = gleak; diff --git a/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/MIT.LICENSE b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/MIT.LICENSE new file mode 100644 index 0000000..7c435ba --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2011 Pivotal Labs + +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/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine-html.js b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine-html.js new file mode 100644 index 0000000..7383401 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine-html.js @@ -0,0 +1,190 @@ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine.css b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine.css new file mode 100644 index 0000000..6583fe7 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine.css @@ -0,0 +1,166 @@ +body { + font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; +} + + +.jasmine_reporter a:visited, .jasmine_reporter a { + color: #303; +} + +.jasmine_reporter a:hover, .jasmine_reporter a:active { + color: blue; +} + +.run_spec { + float:right; + padding-right: 5px; + font-size: .8em; + text-decoration: none; +} + +.jasmine_reporter { + margin: 0 5px; +} + +.banner { + color: #303; + background-color: #fef; + padding: 5px; +} + +.logo { + float: left; + font-size: 1.1em; + padding-left: 5px; +} + +.logo .version { + font-size: .6em; + padding-left: 1em; +} + +.runner.running { + background-color: yellow; +} + + +.options { + text-align: right; + font-size: .8em; +} + + + + +.suite { + border: 1px outset gray; + margin: 5px 0; + padding-left: 1em; +} + +.suite .suite { + margin: 5px; +} + +.suite.passed { + background-color: #dfd; +} + +.suite.failed { + background-color: #fdd; +} + +.spec { + margin: 5px; + padding-left: 1em; + clear: both; +} + +.spec.failed, .spec.passed, .spec.skipped { + padding-bottom: 5px; + border: 1px solid gray; +} + +.spec.failed { + background-color: #fbb; + border-color: red; +} + +.spec.passed { + background-color: #bfb; + border-color: green; +} + +.spec.skipped { + background-color: #bbb; +} + +.messages { + border-left: 1px dashed gray; + padding-left: 1em; + padding-right: 1em; +} + +.passed { + background-color: #cfc; + display: none; +} + +.failed { + background-color: #fbb; +} + +.skipped { + color: #777; + background-color: #eee; + display: none; +} + + +/*.resultMessage {*/ + /*white-space: pre;*/ +/*}*/ + +.resultMessage span.result { + display: block; + line-height: 2em; + color: black; +} + +.resultMessage .mismatch { + color: black; +} + +.stackTrace { + white-space: pre; + font-size: .8em; + margin-left: 10px; + max-height: 5em; + overflow: auto; + border: 1px inset red; + padding: 1em; + background: #eef; +} + +.finished-at { + padding-left: 1em; + font-size: .6em; +} + +.show-passed .passed, +.show-skipped .skipped { + display: block; +} + + +#jasmine_content { + position:fixed; + right: 100%; +} + +.runner { + border: 1px solid gray; + display: block; + margin: 5px 0; + padding: 2px 0 2px 10px; +} diff --git a/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine.js b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine.js new file mode 100644 index 0000000..c3d2dc7 --- /dev/null +++ b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine.js @@ -0,0 +1,2476 @@ +var isCommonJS = typeof window == "undefined"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a instanceof jasmine.Matchers.Any) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.Any) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toNotEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + if (this.actual.callCount === 0) { + // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." + ]; + } else { + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) + ]; + } + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toNotContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + var multiplier = Math.pow(10, precision); + var actual = Math.round(this.actual * multiplier); + expected = Math.round(expected * multiplier); + return expected == actual; +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} expected + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.matches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.toString = function() { + return ''; +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + if (this.ppNestLevel_ > 40) { + throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); + } + + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value instanceof jasmine.Matchers.Any) { + this.emitScalar(value.toString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block) { + this.blocks.unshift(block); +}; + +jasmine.Queue.prototype.add = function(block) { + this.blocks.push(block); +}; + +jasmine.Queue.prototype.insertNext = function(block) { + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !this.abort) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this)); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 1, + "build": 0, + "revision": 1315677058 +}; diff --git a/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine_favicon.png b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine_favicon.png new file mode 100644 index 0000000..218f3b4 Binary files /dev/null and b/node_modules/mongodb/node_modules/bson/tools/jasmine-1.1.0/jasmine_favicon.png differ diff --git a/node_modules/mongodb/package.json b/node_modules/mongodb/package.json new file mode 100755 index 0000000..94843a3 --- /dev/null +++ b/node_modules/mongodb/package.json @@ -0,0 +1,214 @@ +{ + "name": "mongodb", + "description": "A node.js driver for MongoDB", + "keywords": [ + "mongodb", + "mongo", + "driver", + "db" + ], + "version": "0.9.9-7", + "author": { + "name": "Christian Amor Kvalheim", + "email": "christkv@gmail.com" + }, + "contributors": [ + { + "name": "Aaron Heckmann" + }, + { + "name": "Christoph Pojer" + }, + { + "name": "Pau Ramon Revilla" + }, + { + "name": "Nathan White" + }, + { + "name": "Emmerman" + }, + { + "name": "Seth LaForge" + }, + { + "name": "Boris Filipov" + }, + { + "name": "Stefan Schärmeli" + }, + { + "name": "Tedde Lundgren" + }, + { + "name": "renctan" + }, + { + "name": "Sergey Ukustov" + }, + { + "name": "Ciaran Jessup" + }, + { + "name": "kuno" + }, + { + "name": "srimonti" + }, + { + "name": "Erik Abele" + }, + { + "name": "Pratik Daga" + }, + { + "name": "Slobodan Utvic" + }, + { + "name": "Kristina Chodorow" + }, + { + "name": "Yonathan Randolph" + }, + { + "name": "Brian Noguchi" + }, + { + "name": "Sam Epstein" + }, + { + "name": "James Harrison Fisher" + }, + { + "name": "Vladimir Dronnikov" + }, + { + "name": "Ben Hockey" + }, + { + "name": "Henrik Johansson" + }, + { + "name": "Simon Weare" + }, + { + "name": "Alex Gorbatchev" + }, + { + "name": "Shimon Doodkin" + }, + { + "name": "Kyle Mueller" + }, + { + "name": "Eran Hammer-Lahav" + }, + { + "name": "Marcin Ciszak" + }, + { + "name": "François de Metz" + }, + { + "name": "Vinay Pulim" + }, + { + "name": "nstielau" + }, + { + "name": "Adam Wiggins" + }, + { + "name": "entrinzikyl" + }, + { + "name": "Jeremy Selier" + }, + { + "name": "Ian Millington" + }, + { + "name": "Public Keating" + }, + { + "name": "andrewjstone" + }, + { + "name": "Christopher Stott" + }, + { + "name": "Corey Jewett" + }, + { + "name": "brettkiefer" + }, + { + "name": "Rob Holland" + }, + { + "name": "Senmiao Liu" + }, + { + "name": "heroic" + }, + { + "name": "gitfy" + }, + { + "name": "Andrew Stone" + }, + { + "name": "John Le Drew" + } + ], + "repository": { + "type": "git", + "url": "git://github.com/christkv/node-mongodb-native.git" + }, + "bugs": { + "email": "node-mongodb-native@googlegroups.com", + "url": "http://github.com/christkv/node-mongodb-native/issues" + }, + "dependencies": { + "bson": "0.0.4" + }, + "devDependencies": { + "dox": "0.2.0", + "uglify-js": "1.2.5", + "ejs": "0.6.1", + "nodeunit": "0.7.3", + "github3": ">=0.3.0", + "markdown": "0.3.1", + "gleak": "0.2.3", + "step": "0.0.5" + }, + "config": { + "native": false + }, + "main": "./lib/mongodb/index", + "directories": { + "lib": "./lib/mongodb" + }, + "engines": { + "node": ">=0.4.0" + }, + "scripts": { + "test": "make test_pure" + }, + "licenses": [ + { + "type": "Apache License, Version 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + } + ], + "_id": "mongodb@0.9.9-7", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "6c4aff5b6c0c66d37dd736585426c43c73b04308" + }, + "_from": "mongodb@" +} diff --git a/node_modules/mongoose/.npmignore b/node_modules/mongoose/.npmignore new file mode 100644 index 0000000..a3db131 --- /dev/null +++ b/node_modules/mongoose/.npmignore @@ -0,0 +1,5 @@ +lib-cov +**.swp +*.sw* +node_modules/ +*.orig diff --git a/node_modules/mongoose/.travis.yml b/node_modules/mongoose/.travis.yml new file mode 100644 index 0000000..f1d0f13 --- /dev/null +++ b/node_modules/mongoose/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.4 + - 0.6 diff --git a/node_modules/mongoose/History.md b/node_modules/mongoose/History.md new file mode 100644 index 0000000..a06b0b4 --- /dev/null +++ b/node_modules/mongoose/History.md @@ -0,0 +1,831 @@ +2.5.13 / 2012-03-22 +=================== + + * fixed; failing validation of unselected required paths (#730,#713) + * fixed; emitting connection error when only one listener (#759) + * fixed; MongooseArray#splice was not returning values (#784) [chrisleishman] + +2.5.12 / 2012-03-21 +=================== + + * fixed; honor the `safe` option in all ensureIndex calls + * updated; node-mongodb-native driver to 0.9.9-7 + +2.5.11 / 2012-03-15 +=================== + + * added; introspection for getters/setters (#745) + * updated; node-mongodb-driver to 0.9.9-5 + * added; tailable method to Query (#769) [holic] + * fixed; Number min/max validation of null (#764) [btamas] + * added; more flexible user/password connection options (#738) [KarneAsada] + +2.5.10 / 2012-03-06 +=================== + + * updated; node-mongodb-native driver to 0.9.9-4 + * added; Query#comment() + * fixed; allow unsetting arrays + * fixed; hooking the set method of subdocuments (#746) + * fixed; edge case in hooks + * fixed; allow $id and $ref in queries (fixes compatibility with mongoose-dbref) (#749) [richtera] + * added; default path selection to SchemaTypes + +2.5.9 / 2012-02-22 +=================== + + * fixed; properly cast nested atomic update operators for sub-documents + +2.5.8 / 2012-02-21 +=================== + + * added; post 'remove' middleware includes model that was removed (#729) [timoxley] + +2.5.7 / 2012-02-09 +=================== + + * fixed; RegExp validators on node >= v0.6.x + +2.5.6 / 2012-02-09 +=================== + + * fixed; emit errors returned from db.collection() on the connection (were being swallowed) + * added; can add multiple validators in your schema at once (#718) [diogogmt] + * fixed; strict embedded documents (#717) + * updated; docs [niemyjski] + * added; pass number of affected docs back in model.update/save + +2.5.5 / 2012-02-03 +=================== + + * fixed; RangeError: maximum call stack exceed error when removing docs with Number _id (#714) + +2.5.4 / 2012-02-03 +=================== + + * fixed; RangeError: maximum call stack exceed error (#714) + +2.5.3 / 2012-02-02 +=================== + + * added; doc#isSelected(path) + * added; query#equals() + * added; beta sharding support + * added; more descript error msgs (#700) [obeleh] + * added; document.modifiedPaths (#709) [ljharb] + * fixed; only functions can be added as getters/setters (#707,704) [ljharb] + +2.5.2 / 2012-01-30 +=================== + + * fixed; rollback -native driver to 0.9.7-3-5 (was causing timeouts and other replica set weirdness) + * deprecated; MongooseNumber (will be moved to a separate repo for 3.x) + * added; init event is emitted on schemas + +2.5.1 / 2012-01-27 +=================== + + * fixed; honor strict schemas in Model.update (#699) + +2.5.0 / 2012-01-26 +=================== + + * added; doc.toJSON calls toJSON on embedded docs when exists [jerem] + * added; populate support for refs of type Buffer (#686) [jerem] + * added; $all support for ObjectIds and Dates (#690) + * fixed; virtual setter calling on instantiation when strict: true (#682) [hunterloftis] + * fixed; doc construction triggering getters (#685) + * fixed; MongooseBuffer check in deepEquals (#688) + * fixed; range error when using Number _ids with `instance.save()` (#691) + * fixed; isNew on embedded docs edge case (#680) + * updated; driver to 0.9.8-3 + * updated; expose `model()` method within static methods + +2.4.10 / 2012-01-10 +=================== + + * added; optional getter application in .toObject()/.toJSON() (#412) + * fixed; nested $operators in $all queries (#670) + * added; $nor support (#674) + * fixed; bug when adding nested schema (#662) [paulwe] + +2.4.9 / 2012-01-04 +=================== + + * updated; driver to 0.9.7-3-5 to fix Linux performance degradation on some boxes + +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..214e6f5 --- /dev/null +++ b/node_modules/mongoose/README.md @@ -0,0 +1,356 @@ +## 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: + + 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/): + + $ npm install mongoose + +Otherwise, you can check it in your repository and then expose it: + + $ git clone git://github.com/LearnBoost/mongoose.git node_modules/mongoose/ + +And install dependency modules written on `package.json`. + +Then you can `require` it: + + 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`. + + 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. + + 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: + + 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 + + var myModel = mongoose.model('ModelName'); + +Or just do it all at once + + var MyModel = mongoose.model('ModelName', mySchema); + +We can then instantiate it, and save it: + + var instance = new MyModel(); + instance.my.key = 'hello'; + instance.save(function (err) { + // + }); + +Or we can find documents from the same collection + + 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). + +**Important!** If you opened a separate connection using `mongoose.createConnection()` but attempt to access the model through `mongoose.model('ModelName')` it will not work as expected since it is not hooked up to an active db connection. In this case access your model through the connection you created: + + var conn = mongoose.createConnection('your connection string'); + var MyModel = conn.model('ModelName', schema); + var m = new MyModel; + m.save() // works + + vs + + var conn = mongoose.createConnection('your connection string'); + var MyModel = mongoose.model('ModelName', schema); + var m = new MyModel; + m.save() // does not work b/c the default connection object was never connected + +## 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: + + // 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: + + 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: + + .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: + + .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: + + 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: + + 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`: + + .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: + + 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 +- [mongoose-ttl](https://github.com/aheckmann/mongoose-ttl) - TTL support +- [mongoose-keywordize](https://github.com/aheckmann/mongoose-keywordize) - auto keywords generation + +## 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-2012 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..d2ad207 --- /dev/null +++ b/node_modules/mongoose/docs/populate.md @@ -0,0 +1,166 @@ +Populate - DBRef-like behavior (experimental) +============================================= + +`ObjectIds` can now refer to another document in a +collection within our database and be populated 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`. + +## Saving a ref (to the parent) + +Below you can see how we "save" a ref in 'story1' back to the _creator. This +is usually all you need to do. + + + 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) ... + }); + }) + +## Populating the refs (to the parent) + +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! + + +## References to the children + +You may find however, if you use the `aaron` object, you are unable to get +a list of the `stories`. This is because no `story` objects were ever 'pushed' +on to `aaron.stories`. + +There are two perspectives to this story. First, it's nice to have aaron know +which are his stories. + + aaron.stories.push(story1); + aaron.save(); + +This allows you do a find & populate like: + + Person + .findOne({ name: 'Aaron' }) + .populate('stories') // <-- only works if you pushed refs to children + .run(function (err, person) { + if (err) .. + + console.log('JSON for person is: ', person); + + }) + +However, it is debatable that you really want two sets of pointers as they +may get out of sync. So you could instead merely find() the documents you +are interested in. + + Story + .find({ _creator: aaron._id }) + .populate('_creator') // <-- not really necessary + .run(function (err, stories) { + if (err) .. + + console.log('The stories JSON is an array: ', stoires); + }) + + + + +## 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..37f7e2a --- /dev/null +++ b/node_modules/mongoose/lib/connection.js @@ -0,0 +1,517 @@ +/** + * 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 + * options.user - username for authentication + * options.pass - password for authentication + * + * 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]; + + // Check hostname for user/pass + } else if (/@/.test(host) && /:/.test(host.split('@')[0])) { + host = host.split('@'); + var auth = host.shift().split(':'); + host = host.pop(); + this.user = auth[0]; + this.pass = auth[1]; + + // user/pass options + } else if (options && options.user && options.pass) { + this.user = options.user; + this.pass = options.pass; + + } 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 && + ('function' == typeof 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..0c80765 --- /dev/null +++ b/node_modules/mongoose/lib/document.js @@ -0,0 +1,1223 @@ +/** + * 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 + , isMongooseObject = utils.isMongooseObject + , 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.schema.options && this.schema.options.strict; + + if ('boolean' === typeof fields) { + this._strictMode = fields; + fields = undefined; + } else { + this._selected = 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, undefined, true); + + this._registerHooks(); + this.doQueue(); + + this.errors = undefined; + this._shardval = 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 + + // 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 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._storeShard(); + + 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); + } + } +}; + +/** + * _storeShard + * + * Stores the current values of the shard keys + * for use later in the doc.save() where clause. + * + * Shard key values do not / are not allowed to change. + * + * @param {Object} document + * @private + */ + +Document.prototype._storeShard = function _storeShard () { + var key = this.schema.options.shardkey; + if (!(key && 'Object' == key.constructor.name)) return; + + var orig = this._shardval = {} + , paths = Object.keys(key) + , len = paths.length + , val + + for (var i = 0; i < len; ++i) { + val = this.getValue(paths[i]); + if (isMongooseObject(val)) { + orig[paths[i]] = val.toObject({ depopulate: true }) + } else if (val.valueOf) { + orig[paths[i]] = val.valueOf(); + } else { + orig[paths[i]] = val; + } + } +} + +// 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 constructing = true === type + , adhoc = type && true !== type + , adhocs + + if (adhoc) { + adhocs = this._adhocPaths || (this._adhocPaths = {}); + adhocs[path] = Schema.interpretAsType(path, type); + } + + if ('string' !== typeof path) { + // new Document({ key: val }) + + 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) + , i = keys.length + , pathtype + , 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, constructing); + } else if (this._strictMode) { + pathtype = this.schema.pathType(prefix + key); + if ('real' === pathtype || 'virtual' === pathtype) { + this.set(prefix + key, path[key], constructing); + } + } else if (undefined !== path[key]) { + this.set(prefix + key, path[key], constructing); + } + } + + return this; + } + } + + var schema; + if ('virtual' === this.schema.pathType(path)) { + schema = this.schema.virtualpath(path); + schema.applySetters(val, this); + return this; + } else { + schema = this._path(path); + } + + var parts = path.split('.') + , obj = this._doc + , self = this + , pathToMark + , subpaths + , subpath + + // When using the $set operator the path to the field must already exist. + // Else mongodb throws: "LEFT_SUBFIELD only supports Object" + + 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(){ + // if this doc is being constructed we should not + // trigger getters. + var cur = constructing ? undefined : self.get(path); + var casted = schema.cast(val, self, false, cur); + val = schema.applySetters(casted, self); + })) { + + if (this.isNew) { + this.markModified(pathToMark); + } else { + var priorVal = this.get(path); + + if (!this.isDirectModified(pathToMark)) { + if (undefined === val && !this.isSelected(path)) { + // special case: + // when a path is not selected in a query its initial + // value will be undefined. + this.markModified(pathToMark); + } else if (!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; +}; + +/** + * modifiedPaths + * + * Returns the list of paths that have been modified. + * + * If we set `documents.0.title` to 'newTitle' + * then `documents`, `documents.0`, and `documents.0.title` + * are modified. + * + * @api public + * @returns Boolean + */ + +Document.prototype.__defineGetter__('modifiedPaths', function () { + var directModifiedPaths = Object.keys(this._activePaths.states.modify); + + return 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('.')); + }, [])); + }, []); +}); + +/** + * 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 + * @returns Boolean + * @api public + */ + +Document.prototype.isModified = function (path) { + return !!~this.modifiedPaths.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 + * @returns Boolean + * @api public + */ + +Document.prototype.isDirectModified = function (path) { + return (path in this._activePaths.states.modify); +}; + +/** + * Checks if a certain path was initialized + * + * @param {String} path + * @returns Boolean + * @api public + */ + +Document.prototype.isInit = function (path) { + return (path in this._activePaths.states.init); +}; + +/** + * Checks if a path was selected. + * @param {String} path + * @return Boolean + * @api public + */ + +Document.prototype.isSelected = function isSelected (path) { + if (this._selected) { + + if ('_id' === path) { + return 0 !== this._selected._id; + } + + var paths = Object.keys(this._selected) + , i = paths.length + , inclusive = false + , cur + + while (i--) { + cur = paths[i]; + if ('_id' == cur) continue; + inclusive = !! this._selected[cur]; + break; + } + + if (path in this._selected) { + return inclusive; + } + + i = paths.length; + + while (i--) { + cur = paths[i]; + if ('_id' == cur) continue; + + if (0 === cur.indexOf(path + '.')) { + return inclusive; + } + + if (0 === (path + '.').indexOf(cur)) { + return inclusive; + } + } + + return ! inclusive; + } + + return true; +} + +/** + * 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 underscore this method + * @param {Error} error + * @api private + */ + +Document.prototype.error = function (err) { + this._saveError = err; + return this; +}; + +/** + * Executes methods queued from the Schema definition + * + * @TODO underscore this method + * @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 + * + * Available options: + * + * - getters: apply all getters (path and virtual getters) + * - virtuals: apply virtual getters (can override `getters` option) + * + * Example of only applying path getters: + * + * doc.toObject({ getters: true, virtuals: false }) + * + * Example of only applying virtual getters: + * + * doc.toObject({ virtuals: true }) + * + * Example of applying both path and virtual getters: + * + * doc.toObject({ getters: true }) + * + * @return {Object} plain object + * @api public + */ + +Document.prototype.toObject = function (options) { + options || (options = {}); + options.minimize = true; + + var ret = clone(this._doc, options); + + if (options.virtuals || options.getters && false !== options.virtuals) { + applyGetters(this, ret, 'virtuals'); + } + + if (options.getters) { + applyGetters(this, ret, 'paths'); + } + + return ret; +}; + +/** + * Applies virtuals properties to `json`. + * + * @param {Document} self + * @param {Object} json + * @param {String} either `virtuals` or `paths` + * @return json + * @private + */ + +function applyGetters (self, json, type) { + var schema = self.schema + , paths = Object.keys(schema[type]) + , i = paths.length + , path + + while (i--) { + path = paths[i]; + + var parts = path.split('.') + , plen = parts.length + , last = plen - 1 + , branch = json + , part + + for (var ii = 0; ii < plen; ++ii) { + part = parts[ii]; + if (ii === last) { + branch[part] = self.get(path); + } else { + branch = branch[part] || (branch[part] = {}); + } + } + } + + return json; +} + +/** + * JSON.stringify helper. + * + * Implicitly called when a document is passed + * to JSON.stringify() + * + * @return {Object} + * @api public + */ + +Document.prototype.toJSON = function (options) { + if ('undefined' === typeof options) options = {}; + options.json = true; + return this.toObject(options); +}; + +/** + * Helper for console.log + * + * @api public + */ + +Document.prototype.toString = +Document.prototype.inspect = function (options) { + return inspect(this.toObject(options)); +}; + +/** + * 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..06176da --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js @@ -0,0 +1,176 @@ + +/** + * 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) { + if (err) { + // likely a strict mode error + self.conn.emit('error', err); + } else { + 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..75bfda3 --- /dev/null +++ b/node_modules/mongoose/lib/model.js @@ -0,0 +1,917 @@ + +/** + * 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, result) { + if (err) return promise.error(err); + + self._storeShard(); + + var numAffected; + if (result) { + numAffected = result.length + ? result.length + : result; + } else { + numAffected = 0; + } + + self.emit('save', self, numAffected); + promise.complete(self, numAffected); + 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) { + var where = this._where(); + this.collection.update(where, delta, options, complete); + } else { + complete(null); + } + + this.emit('isNew', false); + } +}; + +/** + * 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 { + if (type instanceof MongooseArray || + type instanceof MongooseBuffer) { + type = type.toObject({ depopulate: 1 }); + } else if (type._path) { + type = type.valueOf(); + } else { + // nested object literal + type = utils.clone(type); + } + + if (useSet) { + if (!('$set' in delta)) + delta['$set'] = {}; + + delta['$set'][data.path] = type; + } else + delta[data.path] = type; + } + + return delta; + }, {}); +} + +/** + * _where + * + * Returns a query object which applies shardkeys if + * they exist. + * + * @private + */ + +Model.prototype._where = function _where () { + var where = {}; + + if (this._shardval) { + var paths = Object.keys(this._shardval) + , len = paths.length + + for (var i = 0; i < len; ++i) { + where[paths[i]] = this._shardval[paths[i]]; + } + } + + var id = this._doc._id.valueOf // MongooseNumber + ? this._doc._id.valueOf() + : this._doc._id; + + where._id = id; + return where; +} + +/** + * 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) + , where = this._where() + , self = this; + + this.collection.remove(where, tick(function (err) { + if (err) { + this._removing = null; + return promise.error(err); + } + promise.complete(); + self.emit('remove', self); + })); + + 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 + , safe = this.schema.options.safe + , count = indexes.length; + + indexes.forEach(function (index) { + var options = index[1]; + options.safe = safe; + self.collection.ensureIndex(index[0], options, tick(function (err) { + if (err) return self.db.emit('error', err); + --count || self.emit('index'); + })); + }); + + this.schema.emit('init', this); +}; + +/** + * 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', 'model'].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); + query.bind(this, 'find'); + query.select(fields); + + 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 updated (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..29c03ab --- /dev/null +++ b/node_modules/mongoose/lib/query.js @@ -0,0 +1,1527 @@ +/** + * 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 ('$or' === path || '$nor' === path) { + 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) === '$' && k !== '$id' && k !== '$ref'; + }); + + 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; +}; + +/** + * Applies schematype selected options to this query. + * @api private + */ + +Query.prototype._applyPaths = function applyPaths () { + // determine if query is selecting or excluding fields + + var fields = this._fields + , exclude + , keys + , ki + + if (fields) { + keys = Object.keys(fields); + ki = keys.length; + + while (ki--) { + if ('_id' == keys[ki]) continue; + exclude = 0 === fields[keys[ki]]; + break; + } + } + + // if selecting, apply default schematype select:true fields + // if excluding, apply schematype select:false fields + // if not specified, apply both + + var selected = [] + , excluded = [] + + this.model.schema.eachPath(function (path, type) { + if ('boolean' != typeof type.selected) return; + ;(type.selected ? selected : excluded).push(path); + }); + + switch (exclude) { + case true: + this.exclude(excluded); + break; + case false: + this.select(selected); + break; + case undefined: + excluded.length && this.exclude(excluded); + selected.length && this.select(selected); + break; + } +} + +/** + * 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; +}; + +/** + * `equals` sugar. + * + * User.where('age').equals(49); + * + * Same as + * + * User.where('age', 49); + * + * @param {object} val + * @return {Query} + * @api public + */ + +Query.prototype.equals = function equals (val) { + var path = this._currPath; + if (!path) throw new Error('equals() must be used after where()'); + this._conditions[path] = val; + return this; +} + +/** + * $or + */ + +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; +} + +/** + * $nor + */ + +Query.prototype.nor = +Query.prototype.$nor = function $nor (array) { + var nor = this._conditions.$nor || (this._conditions.$nor = []); + if (!Array.isArray(array)) array = [array]; + nor.push.apply(nor, array); + return this; +} + +/** + * $gt, $gte, $lt, $lte, $ne, $in, $nin, $all, $regex, $size, $maxDistance + * + * Can be used on Numbers or Dates. + * + * Thing.where('type').$nin(array) + */ + +'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; + }; +}); + +/** + * notEqualTo + * + * alias of `query.$ne()` + */ + +Query.prototype.notEqualTo = Query.prototype.ne; + +/** + * $mod, $near + */ + +;['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; + }; +}); + +/** + * $exists + */ + +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; +}; + +/** + * $elemMatch + */ + +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; +}; + +/** + * select + * + * _also aliased as fields()_ + * + * 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; +}; + +/** + * only + * + * 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; +}; + +/** + * exclude + * + * 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; +}; + +/** + * $slice() + */ + +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]; +}; + +/** + * sort + * + * 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; +}; + +/** + * asc + * + * Sorts ascending. + * + * query.asc('name', 'age'); + */ + +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; +}; + +/** + * desc + * + * Sorts descending. + * + * query.desc('name', 'age'); + */ + +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, comment + * + * Sets these associated options. + * + * query.comment('feed query'); + */ + +;['limit', 'skip', 'maxscan', 'snapshot', 'batchSize', 'comment'].forEach( function (method) { + Query.prototype[method] = function (v) { + this.options[method] = v; + return this; + }; +}); + +/** + * hint + * + * Sets 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; +}; + +/** + * slaveOk + * + * Sets slaveOk option. + * + * new Query().slaveOk() <== true + * new Query().slaveOk(true) + * new Query().slaveOk(false) + * + * @param {Boolean} v (defaults to true) + * @api public + */ + +Query.prototype.slaveOk = function (v) { + this.options.slaveOk = arguments.length ? !!v : true; + return this; +}; + +/** + * tailable + * + * Sets tailable option. + * + * new Query().tailable() <== true + * new Query().tailable(true) + * new Query().tailable(false) + * + * @param {Boolean} v (defaults to true) + * @api public + */ + +Query.prototype.tailable = function (v) { + this.options.tailable = arguments.length ? !!v : true; + return this; +}; + +/** + * execFind + * + * @api private + */ + +Query.prototype.execFind = function (callback) { + var model = this.model + , promise = new Promise(callback); + + try { + this.cast(model); + } catch (err) { + return promise.error(err); + } + + // apply default schematype path selections + this._applyPaths(); + + var self = this + , castQuery = this._conditions + , options = this._optionsForExec(model) + + var fields = utils.clone(options.fields = this._fields); + + 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; +}; + +/** + * each() + * + * 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; +} + +/** + * findOne + * + * 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; + } + + // apply default schematype path selections + this._applyPaths(); + + 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; +}; + +/** + * count + * + * 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; +}; + +/** + * distinct + * + * 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 +} + +/** + * update + * + * 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 strict = this.model.schema.options.strict + , 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 (strict && !schema) { + // path is not in our strict schema. do not include + delete obj[key]; + } else { + 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); + + if (strict && !schema) { + delete obj[key]; + } else { + obj[key] = this._castUpdateVal(schema, val, op, key); + } + } + } +} + +/** + * Casts `val` according to `schema` and atomic `op`. + * + * @param {Schema} schema + * @param {Object} val + * @param {String} op - the atomic operator ($pull, $set, etc) + * @param {String} [$conditional] + * @private + */ + +Query.prototype._castUpdateVal = function _castUpdateVal (schema, val, op, $conditional) { + 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]; + } + } + + if (op in numberOps) return Number(val); + if (/^\$/.test($conditional)) return schema.castForQuery($conditional, val); + return 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) +} + +/** + * remove + * + * 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; +}; + +/** + * populate + * + * 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; + +/** + * stream + * + * 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..1dda865 --- /dev/null +++ b/node_modules/mongoose/lib/schema.js @@ -0,0 +1,565 @@ +/** + * 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) { + 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]]; + } + + // update the tree + var subpaths = path.split(/\./) + , last = subpaths.pop() + , branch = this.tree; + + subpaths.forEach(function(path) { + if (!branch[path]) branch[path] = {}; + branch = branch[path]; + }); + + branch[last] = utils.clone(obj); + + 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); + } + + if (undefined == Types[type.name]) { + throw new TypeError('Undefined type at `' + path + + '`\n Did you try nesting Schemas? ' + + 'You can only nest using refs or arrays.'); + } + + 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..6458a73 --- /dev/null +++ b/node_modules/mongoose/lib/schema/array.js @@ -0,0 +1,232 @@ +/** + * 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]; + } + + val = val.map(function (v) { + if (v && 'Object' === v.constructor.name) { + var o = {}; + o[this.path] = v; + var query = new Query(o); + query.cast(this.casterConstructor); + return query._conditions[this.path]; + } + return v; + }, this); + + 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..64e12a2 --- /dev/null +++ b/node_modules/mongoose/lib/schema/buffer.js @@ -0,0 +1,106 @@ +/** + * 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, 'Buffer'); +}; + +/** + * 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, init) { + if (SchemaType._isRef(this, value, init)) return value; + + 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..3a7bda8 --- /dev/null +++ b/node_modules/mongoose/lib/schema/date.js @@ -0,0 +1,116 @@ + +/** + * 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 + , '$all': 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..38276ff --- /dev/null +++ b/node_modules/mongoose/lib/schema/documentarray.js @@ -0,0 +1,141 @@ + +/** + * 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 && 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); + subdoc.set(value[i]); + // if set() is hooked it will have no return value + // see gh-746 + value[i] = subdoc; + } + } + } + + 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..75a14f6 --- /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 === null || 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 === null || 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..a83f95d --- /dev/null +++ b/node_modules/mongoose/lib/schema/objectid.js @@ -0,0 +1,126 @@ +/** + * 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 + , '$all': handleArray +}; + +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..f2accb6 --- /dev/null +++ b/node_modules/mongoose/lib/schematype.js @@ -0,0 +1,428 @@ +/** + * 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; + this.selected; + + 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) { + if ('function' != typeof fn) + throw new Error('A setter must be a function.'); + this.setters.push(fn); + return this; +}; + +/** + * Adds a getter + * + * @param {Function} getter + * @api public + */ + +SchemaType.prototype.get = function (fn) { + if ('function' != typeof fn) + throw new Error('A getter must be a function.'); + this.getters.push(fn); + return this; +}; + +/** + * ##validate + * + * Adds validators. + * + * Examples: + * + * function validator () { ... } + * + * var single = [validator, 'failed'] + * new Schema({ name: { type: String, validate: single }}); + * + * var many = [ + * { validator: validator, msg: 'uh oh' } + * , { validator: fn, msg: 'failed' } + * ] + * new Schema({ name: { type: String, validate: many }}); + * + * @param {Object} validator + * @param {String} optional error message + * @api public + */ + +SchemaType.prototype.validate = function (obj, error) { + if ('function' == typeof obj || obj && 'RegExp' === obj.constructor.name) { + this.validators.push([obj, error]); + return this; + } + + var i = arguments.length + , arg + + while (i--) { + arg = arguments[i]; + this.validators.push([arg.validator, arg.msg]); + } + + 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) { + // in here, `this` refers to the validating document. + // no validation when this path wasn't selected in the query. + if ('isSelected' in this && + !this.isSelected(self.path) && + !this.isModified(self.path)) return true; + 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, this); + 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, this); + 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; +}; + +/** + * ## select + * + * Set default select() behavior for this path. True if + * this path should always be included in the results, + * false if it should be excluded by default. This setting + * can be overridden at the query level. + * + * T = db.model('T', new Schema({ x: { type: String, select: true }})); + * T.find(..); // x will always be selected .. + * // .. unless overridden; + * T.find().select({ x: 0 }).exec(); + */ + +SchemaType.prototype.select = function select (val) { + this.selected = !! val; +} + +/** + * 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(err = new ValidatorError(path, msg)); + } + } + + 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/array.js b/node_modules/mongoose/lib/types/array.js new file mode 100644 index 0000000..67f88b6 --- /dev/null +++ b/node_modules/mongoose/lib/types/array.js @@ -0,0 +1,422 @@ + +/** + * 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 + */ + +MongooseArray.prototype.$push = +MongooseArray.prototype.push = function () { + var values = [].map.call(arguments, this._cast, this) + , ret = [].push.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 = [].map.call(arguments, this._cast, this) + , ret = [].push.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(); +}; + +/** + * Atomically shifts the array. + * + * Only works once per doc.save(). Calling this mulitple + * times on an array before saving is the same as calling + * it once. + * + * @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 = [].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 = [].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 + */ + +MongooseArray.prototype.splice = function () { + var result = [].splice.apply(this, arguments); + this._markModified(); + return result; +}; + +/** + * Non-atomically 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 + */ + +MongooseArray.prototype.unshift = function () { + var values = [].map.call(arguments, this._cast, this); + [].unshift.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 = [].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) { + [].push.call(this, v); + this._registerAtomic('$addToSet', v); + [].push.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..dff1fe3 --- /dev/null +++ b/node_modules/mongoose/lib/types/embedded.js @@ -0,0 +1,103 @@ + +/** + * 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); +}; + +/** + * Noop. Does not actually save the doc to the db. + * + * @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..b2a69b6 --- /dev/null +++ b/node_modules/mongoose/lib/types/number.js @@ -0,0 +1,84 @@ + +/** + * Module dependencies. + */ + +/** + * MongooseNumber constructor. + * + * @param {Object} value to pass to Number + * @param {Document} parent document + * @deprecated (remove in 3.x) + * @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..2d8f8ee --- /dev/null +++ b/node_modules/mongoose/lib/utils.js @@ -0,0 +1,434 @@ +/** + * 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 (Buffer.isBuffer(a)) { + if (!Buffer.isBuffer(b)) return false; + if (a.length !== b.length) return false; + for (var i = 0, len = a.length; i < len; ++i) { + if (a[i] !== b[i]) return false; + } + return true; + } + + 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)) { + if (options && options.json && 'function' === typeof obj.toJSON) { + return obj.toJSON(options); + } else { + 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..6f8c772 --- /dev/null +++ b/node_modules/mongoose/package.json @@ -0,0 +1,52 @@ +{ + "name": "mongoose", + "description": "Mongoose MongoDB ODM", + "version": "2.5.13", + "author": { + "name": "Guillermo Rauch", + "email": "guillermo@learnboost.com" + }, + "keywords": [ + "mongodb", + "mongoose", + "orm", + "data", + "datastore", + "nosql", + "odm", + "sql" + ], + "dependencies": { + "hooks": "0.2.0", + "mongodb": "0.9.9-7" + }, + "devDependencies": { + "should": "0.2.1", + "gleak": "0.2.1", + "cli-table": "0.0.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" + }, + "_id": "mongoose@2.5.13", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "d70f044f8d507d0b8e42f6f8cbc300db7c61304c" + }, + "_from": "mongoose@>= 2.5.1" +} 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/.npmignore b/node_modules/mongoose/support/expresso/.npmignore new file mode 100644 index 0000000..432563f --- /dev/null +++ b/node_modules/mongoose/support/expresso/.npmignore @@ -0,0 +1,3 @@ +.DS_Store +lib-cov +*.seed \ No newline at end of file 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..ffb3783 --- /dev/null +++ b/node_modules/mongoose/test/common.js @@ -0,0 +1,145 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('../') + , should = require('should') + , Table = require('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) { + var uri; + + if (options && options.uri) { + uri = options.uri; + delete options.uri; + } else { + uri = process.env.MONGOOSE_TEST_URI || + 'mongodb://localhost/mongoose_test' + } + + return mongoose.createConnection(uri, 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..92c162a --- /dev/null +++ b/node_modules/mongoose/test/connection.test.js @@ -0,0 +1,310 @@ + +/** + * 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(); + + // Test connecting using user/pass in hostname + db = mongoose.createConnection('aaron:psw@localhost', 'fake', 27000); + 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(); + + // Test connecting using user/pass options + db = mongoose.createConnection('localhost', 'fake', 27000, {user: 'aaron', pass: 'psw'}); + 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(); + + // Test connecting using only user option - which shouldn't work + db = mongoose.createConnection('localhost', 'fake', 27000, {user: 'no_pass'}); + 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(); + }, + + '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(); + }, + + 'connection error event fires with one listener': function (exit) { + var db= start({ uri: 'mongodb://localasdfads/fakeeee'}) + , called = false; + db.on('error', function () { + // this callback has no params which triggered the bug #759 + called = true; + }); + exit(function () { + called.should.be.true; + }); + } + +}; 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.strict.test.js b/node_modules/mongoose/test/document.strict.test.js new file mode 100644 index 0000000..f9b8118 --- /dev/null +++ b/node_modules/mongoose/test/document.strict.test.js @@ -0,0 +1,178 @@ + +/** + * 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; + +module.exports = { + + '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){ + db.close(); + doc.content.should.equal('sample2'); + doc.should.not.have.property('rouge'); + should.not.exist(doc.rouge); + }); + }, + + 'embedded doc 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('EmbeddedLax', new Schema({ dox: [lax] }), 'embdoc'+random()); + var Strict = db.model('EmbeddedStrict', new Schema({ dox: [strict] }), 'embdoc'+random()); + + var l = new Lax({ dox: [{content: 'sample', rouge: 'data'}] }); + l.dox[0]._strictMode.should.be.false; + l = l.dox[0].toObject(); + l.content.should.equal('sample') + l.rouge.should.equal('data'); + should.exist(l.rouge); + + var s = new Strict({ dox: [{content: 'sample', rouge: 'data'}] }); + s.dox[0]._strictMode.should.be.true; + s = s.dox[0].toObject(); + s.should.have.property('ts'); + s.content.should.equal('sample'); + s.should.not.have.property('rouge'); + should.not.exist(s.rouge); + + // testing init + var s3 = new Strict(); + s3.init({dox: [{content: 'sample', rouge: 'data'}]}); + var s3obj = s3.toObject(); + s3.dox[0].content.should.equal('sample'); + s3.dox[0].should.not.have.property('rouge'); + should.not.exist(s3.dox[0].rouge); + + // strict on create + Strict.create({dox:[{content: 'sample2', rouge: 'data'}]}, function(err, doc){ + db.close(); + doc.dox[0].content.should.equal('sample2'); + doc.dox[0].should.not.have.property('rouge'); + should.not.exist(doc.dox[0].rouge); + }); + }, + + 'strict mode virtuals': function () { + var db = start(); + + var getCount = 0 + , setCount = 0; + + var strictSchema = new Schema({ + email: String + , prop: String + }, {strict: true}); + + strictSchema + .virtual('myvirtual') + .get(function() { + getCount++; + return 'ok'; + }) + .set(function(v) { + setCount++; + this.prop = v; + }); + + var StrictModel = db.model('StrictVirtual', strictSchema); + + var strictInstance = new StrictModel({ + email: 'hunter@skookum.com' + , myvirtual: 'test' + }); + + db.close(); + getCount.should.equal(0); + setCount.should.equal(1); + + strictInstance.myvirtual = 'anotherone'; + var myvirtual = strictInstance.myvirtual; + + getCount.should.equal(1); + setCount.should.equal(2); + } +} diff --git a/node_modules/mongoose/test/document.test.js b/node_modules/mongoose/test/document.test.js new file mode 100644 index 0000000..7c1b244 --- /dev/null +++ b/node_modules/mongoose/test/document.test.js @@ -0,0 +1,912 @@ + +/** + * 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 em = new Schema({ title: String, body: String }); +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 + } + } + , em: [em] +}); + +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); + }, + + 'toObject options': function () { + var doc = new TestDocument(); + + doc.init({ + test : 'test' + , oids : [] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + } + }); + + var clone = doc.toObject({ getters: true, virtuals: false }); + + clone.test.should.eql('test'); + clone.oids.should.be.an.instanceof(Array); + (clone.nested.age == 5).should.be.true; + DocumentObjectId.toString(clone.nested.cool).should.eql('4c6c2d6240ced95d0e00003c'); + clone.nested.path.should.eql('5my path'); + should.equal(undefined, clone.nested.agePlus2); + + clone = doc.toObject({ virtuals: true }); + + clone.test.should.eql('test'); + clone.oids.should.be.an.instanceof(Array); + (clone.nested.age == 5).should.be.true; + DocumentObjectId.toString(clone.nested.cool).should.eql('4c6c2d6240ced95d0e00003c'); + clone.nested.path.should.eql('my path'); + clone.nested.agePlus2.should.eql(7); + + clone = doc.toObject({ getters: true }); + + clone.test.should.eql('test'); + clone.oids.should.be.an.instanceof(Array); + (clone.nested.age == 5).should.be.true; + DocumentObjectId.toString(clone.nested.cool).should.eql('4c6c2d6240ced95d0e00003c'); + clone.nested.path.should.eql('5my path'); + clone.nested.agePlus2.should.eql(7); + }, + + '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; + }); + }, + + // gh-746 + 'hooking set works with document arrays': function () { + var db = start(); + + var child = new Schema({ text: String }); + + child.pre('set', function (next, path, value, type) { + next(path, value, type); + }); + + var schema = new Schema({ + name: String + , e: [child] + }); + + var S = db.model('docArrayWithHookedSet', schema); + + var s = new S({ name: "test" }); + s.e = [{ text: 'hi' }]; + s.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }, + + '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: [], em: [] }); + }, + + // 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 + }, + + 'isSelected': function () { + var doc = new TestDocument(); + + doc.init({ + test : 'test' + , numbers : [4,5,6,7] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + , deep : { x: 'a string' } + } + , notapath: 'i am not in the schema' + , em: [{ title: 'gocars' }] + }); + + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; // even if no data + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.true; + doc.isSelected('nested.cool').should.be.true; + doc.isSelected('nested.path').should.be.true; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.true; // not a path + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; // not a path + doc.isSelected('noway').should.be.true; // not a path + doc.isSelected('notapath').should.be.true; // not a path but in the _doc + doc.isSelected('em').should.be.true; + doc.isSelected('em.title').should.be.true; + doc.isSelected('em.body').should.be.true; + doc.isSelected('em.nonpath').should.be.true; // not a path + + var selection = { + 'test': 1 + , 'numbers': 1 + , 'nested.deep': 1 + , 'oids': 1 + } + + doc = new TestDocument(undefined, selection); + + doc.init({ + test : 'test' + , numbers : [4,5,6,7] + , nested : { + deep : { x: 'a string' } + } + }); + + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; // even if no data + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.false; + doc.isSelected('nested.cool').should.be.false; + doc.isSelected('nested.path').should.be.false; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.false; + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; + doc.isSelected('noway').should.be.false; + doc.isSelected('notapath').should.be.false; + doc.isSelected('em').should.be.false; + doc.isSelected('em.title').should.be.false; + doc.isSelected('em.body').should.be.false; + doc.isSelected('em.nonpath').should.be.false; + + var selection = { + 'em.title': 1 + } + + doc = new TestDocument(undefined, selection); + + doc.init({ + em: [{ title: 'one' }] + }); + + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.false; + doc.isSelected('numbers').should.be.false; + doc.isSelected('oids').should.be.false; + doc.isSelected('nested').should.be.false; + doc.isSelected('nested.age').should.be.false; + doc.isSelected('nested.cool').should.be.false; + doc.isSelected('nested.path').should.be.false; + doc.isSelected('nested.deep').should.be.false; + doc.isSelected('nested.nope').should.be.false; + doc.isSelected('nested.deep.x').should.be.false; + doc.isSelected('nested.deep.x.no').should.be.false; + doc.isSelected('nested.deep.y').should.be.false; + doc.isSelected('noway').should.be.false; + doc.isSelected('notapath').should.be.false; + doc.isSelected('em').should.be.true; + doc.isSelected('em.title').should.be.true; + doc.isSelected('em.body').should.be.false; + doc.isSelected('em.nonpath').should.be.false; + + var selection = { + 'em': 0 + } + + doc = new TestDocument(undefined, selection); + doc.init({ + test : 'test' + , numbers : [4,5,6,7] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + , deep : { x: 'a string' } + } + , notapath: 'i am not in the schema' + }); + + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.true; + doc.isSelected('nested.cool').should.be.true; + doc.isSelected('nested.path').should.be.true; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.true; + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; + doc.isSelected('noway').should.be.true; + doc.isSelected('notapath').should.be.true; + doc.isSelected('em').should.be.false; + doc.isSelected('em.title').should.be.false; + doc.isSelected('em.body').should.be.false; + doc.isSelected('em.nonpath').should.be.false; + + var selection = { + '_id': 0 + } + + doc = new TestDocument(undefined, selection); + doc.init({ + test : 'test' + , numbers : [4,5,6,7] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + , deep : { x: 'a string' } + } + , notapath: 'i am not in the schema' + }); + + doc.isSelected('_id').should.be.false; + doc.isSelected('nested.deep.x.no').should.be.true; + + doc = new TestDocument({ test: 'boom' }); + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.true; + doc.isSelected('nested.cool').should.be.true; + doc.isSelected('nested.path').should.be.true; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.true; + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; + doc.isSelected('noway').should.be.true; + doc.isSelected('notapath').should.be.true; + doc.isSelected('em').should.be.true; + doc.isSelected('em.title').should.be.true; + doc.isSelected('em.body').should.be.true; + doc.isSelected('em.nonpath').should.be.true; + + doc = new TestDocument({ test: 'boom' }, true); + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.true; + doc.isSelected('nested.cool').should.be.true; + doc.isSelected('nested.path').should.be.true; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.true; + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; + doc.isSelected('noway').should.be.true; + doc.isSelected('notapath').should.be.true; + doc.isSelected('em').should.be.true; + doc.isSelected('em.title').should.be.true; + doc.isSelected('em.body').should.be.true; + doc.isSelected('em.nonpath').should.be.true; + }, + + 'unselected required fields should pass validation': function () { + var db = start() + , Tschema = new Schema({ name: String, req: { type: String, required: true }}) + , T = db.model('unselectedRequiredFieldValidation', Tschema); + + var t = new T({ name: 'teeee', req: 'i am required' }); + t.save(function (err) { + should.strictEqual(null, err); + T.findById(t).select('name').exec(function (err, t) { + should.strictEqual(null, err); + should.strictEqual(undefined, t.req); + t.name = 'wooo'; + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t).select('name').exec(function (err, t) { + should.strictEqual(null, err); + t.req = undefined; + t.save(function (err) { + err = String(err); + var invalid = /Validator "required" failed for path req/.test(err); + invalid.should.be.true; + t.req = 'it works again' + t.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }); + }); + }); + } +}; 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..2b3d13d --- /dev/null +++ b/node_modules/mongoose/test/drivers/node-mongodb-native/collection.test.js @@ -0,0 +1,63 @@ + +/** + * 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, safe: true }, function (err) { + db.close(); + ;/E11000 duplicate key error index/.test(err.message).should.equal(true); + }); + }); + } +}; 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..db0625d --- /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[30m', '\n', 'You\'re not testing 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..2f5d53e --- /dev/null +++ b/node_modules/mongoose/test/model.querying.test.js @@ -0,0 +1,2352 @@ + +/** + * 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(); + } + }); + }, + + 'finding where #nor': function () { + var db = start() + , Mod = db.model('Mod', 'nor_' + 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({$nor: [{num: 1}, {num: 3}]}, function (err, found) { + done(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(two._id); + }); + } + + function test2 () { + Mod.find({ $nor: [{ str: 'two'}, {str:'three'}] }, function (err, found) { + done(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(one._id); + }); + } + + function test3 () { + Mod.find({$nor: [{num: 2}]}).$nor([{ str: 'two' }]).run(function (err, found) { + done(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(one._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.comments = 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); + should.strictEqual(undefined, b.comments); + }); + }); + }); + }); + }); + }); + }, + + '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); + }); + }); + }); + }); + }); + }); + + }); + }, + + 'find using #all with nested #elemMatch': function () { + var db = start() + , P = db.model('BlogPostB', collection + '_nestedElemMatch'); + + var post = new P({ title: "nested elemMatch" }); + post.comments.push({ title: 'comment A' }, { title: 'comment B' }, { title: 'comment C' }) + + var id0 = post.comments[0]._id; + var id1 = post.comments[1]._id; + var id2 = post.comments[2]._id; + + post.save(function (err) { + should.strictEqual(null, err); + + var query0 = { $elemMatch: { _id: id1, title: 'comment B' }}; + var query1 = { $elemMatch: { _id: id2.toString(), title: 'comment C' }}; + + P.findOne({ comments: { $all: [query0, query1] }}, function (err, p) { + db.close(); + should.strictEqual(null, err); + p.id.should.equal(post.id); + }); + }); + }, + + 'find using #or with nested #elemMatch': function () { + var db = start() + , P = db.model('BlogPostB', collection); + + var post = new P({ title: "nested elemMatch" }); + post.comments.push({ title: 'comment D' }, { title: 'comment E' }, { title: 'comment F' }) + + var id0 = post.comments[0]._id; + var id1 = post.comments[1]._id; + var id2 = post.comments[2]._id; + + post.save(function (err) { + should.strictEqual(null, err); + + var query0 = { comments: { $elemMatch: { title: 'comment Z' }}}; + var query1 = { comments: { $elemMatch: { _id: id1.toString(), title: 'comment E' }}}; + + P.findOne({ $or: [query0, query1] }, function (err, p) { + db.close(); + should.strictEqual(null, err); + p.id.should.equal(post.id); + }); + }); + }, + + 'find using #nor with nested #elemMatch': function () { + var db = start() + , P = db.model('BlogPostB', collection + '_norWithNestedElemMatch'); + + var p0 = { title: "nested $nor elemMatch1", comments: [] }; + + var p1 = { title: "nested $nor elemMatch0", comments: [] }; + p1.comments.push({ title: 'comment X' }, { title: 'comment Y' }, { title: 'comment W' }) + + P.create(p0, p1, function (err, post0, post1) { + should.strictEqual(null, err); + + var id = post1.comments[1]._id; + + var query0 = { comments: { $elemMatch: { title: 'comment Z' }}}; + var query1 = { comments: { $elemMatch: { _id: id.toString(), title: 'comment Y' }}}; + + P.find({ $nor: [query0, query1] }, function (err, posts) { + db.close(); + should.strictEqual(null, err); + posts.length.should.equal(1); + posts[0].id.should.equal(post0.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); + }); + }); + }); + }, + + // gh-690 + 'using $all with ObjectIds': function () { + var db = start() + + var SSchema = new Schema({ name: String }); + var PSchema = new Schema({ sub: [SSchema] }); + + var P = db.model('usingAllWithObjectIds', PSchema); + var sub = [{ name: 'one' }, { name: 'two' }, { name: 'three' }]; + + P.create({ sub: sub }, function (err, p) { + should.strictEqual(null, err); + + var o0 = p.sub[0]._id; + var o1 = p.sub[1]._id; + var o2 = p.sub[2]._id; + + P.findOne({ 'sub._id': { $all: [o1, o2] }}, function (err, doc) { + should.strictEqual(null, err); + doc.id.should.equal(p.id); + + P.findOne({ 'sub._id': { $all: [o0, new DocumentObjectId] }}, function (err, doc) { + should.strictEqual(null, err); + should.equal(false, !!doc); + + P.findOne({ 'sub._id': { $all: [o2] }}, function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.id.should.equal(p.id); + }); + }); + }); + }); + }, + + 'using $all with Dates': function () { + var db = start() + + var SSchema = new Schema({ d: Date }); + var PSchema = new Schema({ sub: [SSchema] }); + + var P = db.model('usingAllWithDates', PSchema); + var sub = [ + { d: new Date } + , { d: new Date(Date.now()-10000) } + , { d: new Date(Date.now()-30000) } + ]; + + P.create({ sub: sub }, function (err, p) { + should.strictEqual(null, err); + + var o0 = p.sub[0].d; + var o1 = p.sub[1].d; + var o2 = p.sub[2].d; + + P.findOne({ 'sub.d': { $all: [o1, o2] }}, function (err, doc) { + should.strictEqual(null, err); + doc.id.should.equal(p.id); + + P.findOne({ 'sub.d': { $all: [o0, new Date] }}, function (err, doc) { + should.strictEqual(null, err); + should.equal(false, !!doc); + + P.findOne({ 'sub.d': { $all: [o2] }}, function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.id.should.equal(p.id); + }); + }); + }); + }); + }, + + 'excluding paths by schematype': function () { + var db =start() + + var schema = new Schema({ + thin: Boolean + , name: { type: String, select: false } + }); + + var S = db.model('ExcludingBySchemaType', schema); + S.create({ thin: true, name: 'the excluded' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('the excluded'); + S.findById(s, function (err, s) { + db.close(); + should.strictEqual(null, err); + s.isSelected('name').should.be.false; + should.strictEqual(undefined, s.name); + }); + }); + }, + + 'excluding paths through schematype': function () { + var db =start() + + var schema = new Schema({ + thin: Boolean + , name: { type: String, select: false} + }); + + var S = db.model('ExcludingBySchemaType', schema); + S.create({ thin: true, name: 'the excluded' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('the excluded'); + + var pending = 2; + function done (err, s) { + --pending || db.close(); + if (Array.isArray(s)) s = s[0]; + should.strictEqual(null, err); + s.isSelected('name').should.be.false; + should.strictEqual(undefined, s.name); + } + + S.findById(s).exclude('thin').exec(done); + S.find({ _id: s._id }).select('thin').exec(done); + }); + }, + + 'including paths through schematype': function () { + var db =start() + + var schema = new Schema({ + thin: Boolean + , name: { type: String, select: true } + }); + + var S = db.model('IncludingBySchemaType', schema); + S.create({ thin: true, name: 'the included' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('the included'); + + var pending = 2; + function done (err, s) { + --pending || db.close(); + if (Array.isArray(s)) s = s[0]; + should.strictEqual(null, err); + s.isSelected('name').should.be.true; + s.name.should.equal('the included'); + } + + S.findById(s).exclude('thin').exec(done); + S.find({ _id: s._id }).select('thin').exec(done); + }); + }, + + 'overriding schematype select options': function () { + var db =start() + + var selected = new Schema({ + thin: Boolean + , name: { type: String, select: true } + }); + var excluded = new Schema({ + thin: Boolean + , name: { type: String, select: false } + }); + + var S = db.model('OverriddingSelectedBySchemaType', selected); + var E = db.model('OverriddingExcludedBySchemaType', excluded); + + var pending = 4; + + S.create({ thin: true, name: 'the included' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('the included'); + + S.find({ _id: s._id }).select('thin name').exec(function (err, s) { + --pending || db.close(); + s = s[0]; + should.strictEqual(null, err); + s.isSelected('name').should.be.true; + s.isSelected('thin').should.be.true; + s.name.should.equal('the included'); + s.thin.should.be.true; + }); + + S.findById(s).exclude('name').exec(function (err, s) { + --pending || db.close(); + should.strictEqual(null, err); + s.isSelected('name').should.be.false; + s.isSelected('thin').should.be.true; + should.equal(undefined, s.name); + should.equal(true, s.thin); + }) + }); + + E.create({ thin: true, name: 'the excluded' },function (err, e) { + should.strictEqual(null, err); + e.name.should.equal('the excluded'); + + E.find({ _id: e._id }).select('thin name').exec(function (err, e) { + --pending || db.close(); + e = e[0]; + should.strictEqual(null, err); + e.isSelected('name').should.be.true; + e.isSelected('thin').should.be.true; + e.name.should.equal('the excluded'); + e.thin.should.be.true; + }); + + E.findById(e).exclude('name').exec(function (err, e) { + --pending || db.close(); + should.strictEqual(null, err); + e.isSelected('name').should.be.false; + e.isSelected('thin').should.be.true; + should.equal(undefined, e.name); + should.equal(true, e.thin); + }) + }); + }, + + 'conflicting schematype path selection should error': function () { + var db =start() + + var schema = new Schema({ + thin: Boolean + , name: { type: String, select: true } + , conflict: { type: String, select: false} + }); + + var S = db.model('ConflictingBySchemaType', schema); + S.create({ thin: true, name: 'bing', conflict: 'crosby' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('bing'); + s.conflict.should.equal('crosby'); + + var pending = 2; + function done (err, s) { + --pending || db.close(); + if (Array.isArray(s)) s = s[0]; + should.equal(true, !!err); + } + + S.findById(s).exec(done); + S.find({ _id: s._id }).exec(done); + }); + }, +}; 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..967b6e4 --- /dev/null +++ b/node_modules/mongoose/test/model.ref.test.js @@ -0,0 +1,1528 @@ + +/** + * 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); + }); + }); + }); + }); + }, + + // gh-675 + 'toJSON should also be called for refs': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users) + + User.prototype._toJSON = User.prototype.toJSON; + User.prototype.toJSON = function() { + var res = this._toJSON(); + res.was_in_to_json = true; + return res; + } + + BlogPost.prototype._toJSON = BlogPost.prototype.toJSON; + BlogPost.prototype.toJSON = function() { + var res = this._toJSON(); + res.was_in_to_json = true; + return res; + } + + User.create({ + name : 'Jerem' + , email : 'jerem@jolicloud.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Ping Pong' + , _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); + + var json = post.toJSON(); + json.was_in_to_json.should.equal(true); + json._creator.was_in_to_json.should.equal(true); + }); + }); + }); + }, + + // gh-686 + 'populate should work on Buffer _ids': function () { + var db = start(); + + var UserSchema = new Schema({ + _id: Buffer + , name: String + }) + + var NoteSchema = new Schema({ + author: { type: Buffer, ref: 'UserWithBufferId' } + , body: String + }) + + var User = db.model('UserWithBufferId', UserSchema, random()) + var Note = db.model('NoteWithBufferId', NoteSchema, random()) + + var alice = new User({_id: new mongoose.Types.Buffer('YWxpY2U=', 'base64'), 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'); + }); + }); + }) + } +}; \ No newline at end of file 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..22c7433 --- /dev/null +++ b/node_modules/mongoose/test/model.test.js @@ -0,0 +1,4682 @@ + +/** + * 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); + }, + + 'mongoose.model!init emits init event on schema': function () { + var db = start() + , schema = new Schema({ name: String }) + , model + + schema.on('init', function (model_) { + model = model_; + }); + + var Named = db.model('EmitInitOnSchema', schema); + db.close(); + model.should.equal(Named); + }, + + '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; + }); + }); + }, + + 'test isNew on embedded documents added after saving': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ title: 'hocus pocus' }) + post.save(function (err) { + post.isNew.should.be.false; + 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.save(function (err) { + 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; + db.close(); + }); + }); + }, + + '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-662 + 'test nested structure created by merging': function() { + var db = start(); + + var MergedSchema = new Schema({ + a: { + foo: String + } + }); + + MergedSchema.add({ + a: { + b: { + bar: String + } + } + }); + + mongoose.model('Merged', MergedSchema); + var Merged = db.model('Merged', 'merged_' + Math.random()); + + var merged = new Merged({ + a: { + foo: 'baz' + , b: { + bar: 'qux' + } + } + }); + + merged.save(function(err) { + should.strictEqual(null, err); + Merged.findById(merged.id, function(err, found) { + db.close(); + should.strictEqual(null, err); + found.a.foo.should.eql('baz'); + found.a.b.bar.should.eql('qux'); + }); + }); + }, + + // gh-714 + 'modified nested objects which contain MongoseNumbers should not cause a RangeError on save': function () { + var db =start() + + var schema = new Schema({ + nested: { + num: Number + } + }); + + var M = db.model('NestedObjectWithMongooseNumber', schema); + var m = new M; + m.nested = null; + m.save(function (err) { + should.strictEqual(null, err); + + M.findById(m, function (err, m) { + should.strictEqual(null, err); + m.nested.num = 5; + m.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }, + + // gh-714 pt 2 + 'no RangeError on remove() of a doc with Number _id': function () { + var db = start() + + var MySchema = new Schema({ + _id: { type: Number }, + name: String + }); + + var MyModel = db.model('MyModel', MySchema, 'numberrangeerror'+random()); + + var instance = new MyModel({ + name: 'test' + , _id: 35 + }); + + instance.save(function (err) { + should.strictEqual(null, err); + + MyModel.findById(35, function (err, doc) { + should.strictEqual(null, err); + + doc.remove(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }, + + // 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 () { + var db = start() + + var D = db.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 post = new D({ + username: "nope" + , email: "too" + , password: "short" + }); + + var timesCalled = 0; + + post.save(function (err) { + db.close(); + 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'); + + }); + }, + + '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 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(); + }, + + // gh-685 + 'getters should not be triggered at construction': function () { + var db = start() + , called = false + + db.close(); + + var schema = new mongoose.Schema({ + number: { + type:Number + , set: function(x){return x/2} + , get: function(x){ + called = true; + return x*2; + } + } + }); + + var A = mongoose.model('gettersShouldNotBeTriggeredAtConstruction', schema); + + var a = new A({ number: 100 }); + called.should.be.false; + var num = a.number; + called.should.be.true; + num.valueOf().should.equal(100); + a.getValue('number').valueOf().should.equal(50); + + called = false; + var b = new A; + b.init({ number: 50 }); + called.should.be.false; + num = b.number; + called.should.be.true; + num.valueOf().should.equal(100); + b.getValue('number').valueOf().should.equal(50); + }, + + '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 + , post = undefined; + + schema.post('save', function (arg) { + arg.id.should.equal(post.id) + save = true; + }); + + schema.post('init', function () { + init = true; + }); + + schema.post('remove', function (arg) { + arg.id.should.eql(post.id) + remove = true; + }); + + mongoose.model('PostHookTest', schema); + + var db = start() + , BlogPost = db.model('PostHookTest'); + + 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 throw errors if a callback is not passed': function () { + var db = start(); + + var DefaultErrSchema = new Schema({}); + DefaultErrSchema.pre('save', function (next) { + try { + next(new Error); + } catch (error) { + // throws b/c nothing is listening to the error event + db.close(); + error.should.be.instanceof(Error); + } + }); + var DefaultErr = db.model('DefaultErr1', DefaultErrSchema, 'default_err_' + random()); + new DefaultErr().save(); + }, + + 'Model#save should emit an error on its db if a callback is not passed to it': function () { + var db = start(); + + db.on('error', function (err) { + db.close(); + err.should.be.an.instanceof(Error); + }); + + var DefaultErrSchema = new Schema({}); + DefaultErrSchema.pre('save', function (next) { + next(new Error); + }); + var DefaultErr = db.model('DefaultErr2', DefaultErrSchema, 'default_err_' + random()); + new DefaultErr().save(); + }, + + // once hooks-js merges our fix this will pass + 'calling next() after a thrown error should not work': function () { + var db = start(); + + var s = new Schema({}); + s.methods.funky = function () { + should.strictEqual(false, true, 'reached unreachable code'); + } + + s.pre('funky', function (next) { + db.close(); + try { + next(new Error); + } catch (error) { + error.should.be.instanceof(Error); + next(); + // throws b/c nothing is listening to the error event + } + }); + var Kaboom = db.model('wowNext2xAndThrow', s, 'next2xAndThrow' + random()); + new Kaboom().funky(); + }, + + '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; + }); + }); + }, + + 'should not throw range error when using Number _id and saving existing doc': function () { + var db =start(); + + var T = new Schema({ _id: Number, a: String }); + + var D = db.model('Testing691', T, 'asdf' + random()); + + var d = new D({ _id: 1 }); + d.save(function (err) { + should.strictEqual(null, err); + + D.findById(d._id, function (err, d) { + should.strictEqual(null, err); + + d.a = 'yo'; + d.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }, + + 'number of affected docs should be returned when saving': function () { + var db = start() + var schema = new Schema({ name: String }); + var S = db.model('AffectedDocsAreReturned', schema); + var s = new S({ name: 'aaron' }); + s.save(function (err, doc, affected) { + should.strictEqual(null, err); + affected.should.equal(1); + s.name = 'heckmanananananana'; + s.save(function (err, doc, affected) { + db.close(); + should.strictEqual(null, err); + affected.should.equal(1); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/model.update.test.js b/node_modules/mongoose/test/model.update.test.js new file mode 100644 index 0000000..8e795d5 --- /dev/null +++ b/node_modules/mongoose/test/model.update.test.js @@ -0,0 +1,526 @@ + +/** + * 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(); + +var strictSchema = new Schema({ name: String }, { strict: true }); +mongoose.model('UpdateStrictSchema', strictSchema); + +module.exports = { + + '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) { + should.strictEqual(null, err); + ret.comments.length.should.equal(1); + ret.comments[0].body.should.equal('9000'); + update15(post, ret); + }) + }); + } + + function update15 (post, ret) { + var update = { + $pull: { comments: { body: { $in: [ret.comments[0].body] }} } + } + + 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(0); + update16(post, ret); + }) + }); + } + + function update16 (post, ret) { + ret.comments.$pushAll([{body: 'hi'}, {body:'there'}]); + ret.save(function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(2); + + var update = { + $pull: { comments: { body: { $nin: ['there'] }} } + } + + BlogPost.update({ _id: ret._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); + }) + }); + }) + }); + } + }, + + // gh-699 + 'Model._castUpdate should honor strict schemas': function () { + var db = start(); + var S = db.model('UpdateStrictSchema'); + db.close(); + + var doc = S.find()._castUpdate({ ignore: true }); + Object.keys(doc.$set).length.should.equal(0); + }, + + 'Model.update should honor strict schemas': function () { + var db = start(); + var S = db.model('UpdateStrictSchema'); + var s = new S({ name: 'orange crush' }); + + s.save(function (err) { + should.strictEqual(null, err); + + S.update({ _id: s._id }, { ignore: true }, function (err, affected) { + should.strictEqual(null, err); + affected.should.equal(1); + + S.findById(s._id, function (err, doc) { + db.close(); + should.strictEqual(null, err); + should.not.exist(doc.ignore); + should.not.exist(doc._doc.ignore); + }); + }); + }); + }, + + 'model.update passes number of affected documents': function () { + var db = start() + , B = db.model('BlogPost', 'wwwwowowo'+random()) + + B.create({ title: 'one'},{title:'two'},{title:'three'}, function (err) { + should.strictEqual(null, err); + B.update({}, { title: 'newtitle' }, { multi: true }, function (err, affected) { + db.close(); + should.strictEqual(null, err); + affected.should.equal(3); + }); + }); + } + +} 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..427d6cc --- /dev/null +++ b/node_modules/mongoose/test/query.test.js @@ -0,0 +1,890 @@ + +/** + * 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'}); + }, + + 'setting a value via equals': function () { + var query = new Query(); + query.where('name').equals('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#tailable': function () { + var query = new Query(); + query.tailable(); + query.options.tailable.should.be.true; + + var query = new Query(); + query.tailable(true); + query.options.tailable.should.be.true; + + var query = new Query(); + query.tailable(false); + query.options.tailable.should.be.false; + }, + + 'Query#comment': function () { + var query = new Query; + 'function'.should.equal(typeof query.comment); + query.comment('Lowpass is more fun').should.equal(query); + query.options.comment.should.equal('Lowpass is more fun'); + }, + + '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..93a3545 --- /dev/null +++ b/node_modules/mongoose/test/schema.test.js @@ -0,0 +1,978 @@ + +/** + * 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); + }); + + // null is allowed + Tobi.path('friends').doValidate(null, function(err){ + should.strictEqual(err, null); + }); + }, + + '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 multiple 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': validator, + 'msg': 'validator1' + }, + { + 'validator': validator, + 'msg': 'validator2' + }, + ], + } + }); + + Animal.path('ferret').doValidate(true, function(err){ + should.strictEqual(err, null); + }); + + beforeExit(function(){ + executed.should.eql(2); + }); + }, + + '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, self) { + this.a.should.eql('b'); + self.path.should.eql('name'); + 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, self) { + this.a.should.eql('b'); + self.path.should.eql('name'); + 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); + }, + + 'selected option': function () { + var s = new Schema({ thought: { type: String, select: false }}); + s.path('thought').selected.should.be.false; + + var a = new Schema({ thought: { type: String, select: true }}); + a.path('thought').selected.should.be.true; + }, + + '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`') + }, + + 'add() does not polute existing paths': function () { + var o = { name: String } + var s = new Schema(o); + s.add({ age: Number }, 'name.'); + ;('age' in o.name).should.be.false; + }, + + // gh-700 + 'nested schemas should throw': function () { + var a = new Schema({ title: String }) + , err + + try { + new Schema({ blah: Boolean, a: a }); + } catch (err_) { + err = err_; + } + + should.exist(err); + ;/Did you try nesting Schemas/.test(err.message).should.be.true; + }, + + 'non-function etters throw': function () { + var schema = new Schema({ fun: String }); + var g, s; + + try { + schema.path('fun').get(true); + } catch (err_) { + g = err_; + } + + should.exist(g); + g.message.should.equal('A getter must be a function.'); + + try { + schema.path('fun').set(4); + } catch (err_) { + s = err_; + } + + should.exist(s); + s.message.should.equal('A setter must be a function.'); + } + +}; diff --git a/node_modules/mongoose/test/shard.test.js b/node_modules/mongoose/test/shard.test.js new file mode 100644 index 0000000..2df04c3 --- /dev/null +++ b/node_modules/mongoose/test/shard.test.js @@ -0,0 +1,216 @@ + +var start = require('./common') + , should = require('should') + , random = require('../lib/utils').random + , mongoose = start.mongoose + , Mongoose = mongoose.Mongoose + , Schema = mongoose.Schema; + +var uri = process.env.MONGOOSE_SHARD_TEST_URI; + +if (!uri) { + console.log('\033[30m', '\n', 'You\'re not testing shards!' + , '\n', 'Please set the MONGOOSE_SHARD_TEST_URI env variable.', '\n' + , 'e.g: `mongodb://localhost:27017/database', '\n' + , 'Sharding must already be enabled on your database' + , '\033[39m'); + + exports.r = function expressoHack(){} + return; +} + +var schema = new Schema({ + name: String + , age: Number + , likes: [String] +}, { shardkey: { name: 1, age: 1 }}); + +var collection = 'shardperson_' + random(); +mongoose.model('ShardPerson', schema, collection); + +var db = start({ uri: uri }); +db.on('open', function () { + + // set up a sharded test collection + var P = db.model('ShardPerson', collection); + + var cmd = {}; + cmd.shardcollection = db.name + '.' + collection; + cmd.key = P.schema.options.shardkey; + + P.db.db.executeDbAdminCommand(cmd,function (err, res) { + db.close(); + + if (err) throw err; + + if (!(res && res.documents && res.documents[0] && res.documents[0].ok)) { + throw new Error('could not shard test collection ' + collection); + } + + // assign exports to tell expresso to begin + Object.keys(tests).forEach(function (test) { + exports[test] = tests[test]; + }); + + }); +}); + +var tests = { + + 'can read and write to a shard': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + + P.create({ name: 'ryu', age: 25, likes: ['street fighting']}, function (err, ryu) { + should.strictEqual(null, err); + P.findById(ryu._id, function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.id.should.equal(ryu.id); + }); + }); + }, + + 'save() and remove() works with shard keys transparently': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + + var zangief = new P({ name: 'Zangief', age: 33 }); + zangief.save(function (err) { + should.strictEqual(null, err); + + zangief._shardval.name.should.equal('Zangief'); + zangief._shardval.age.should.equal(33); + + P.findById(zangief._id, function (err, zang) { + should.strictEqual(null, err); + + zang._shardval.name.should.equal('Zangief'); + zang._shardval.age.should.equal(33); + + zang.likes = ['spinning', 'laughing']; + zang.save(function (err) { + should.strictEqual(null, err); + + zang._shardval.name.should.equal('Zangief'); + zang._shardval.age.should.equal(33); + + zang.likes.addToSet('winning'); + zang.save(function (err) { + should.strictEqual(null, err); + zang._shardval.name.should.equal('Zangief'); + zang._shardval.age.should.equal(33); + zang.remove(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }); + }); + }, + + 'inserting to a sharded collection without the full shard key fails': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + + var pending = 6; + + P.create({ name: 'ryu', likes: ['street fighting']}, function (err, ryu) { + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + P.create({ likes: ['street fighting']}, function (err, ryu) { should.exist(err); + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + P.create({ name: 'ryu' }, function (err, ryu) { should.exist(err); + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + P.create({ age: 49 }, function (err, ryu) { should.exist(err); + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + P.create({ likes: ['street fighting'], age: 8 }, function (err, ryu) { + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + var p = new P; + p.save(function (err) { + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + }, + + 'updating a sharded collection without the full shard key fails': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + + P.create({ name: 'ken', age: 27 }, function (err, ken) { + should.strictEqual(null, err); + + P.update({ _id: ken._id }, { likes: ['kicking', 'punching'] }, function (err) { + should.exist(err); + "right object doesn't have full shard key".should.equal(err.message); + + P.update({ _id: ken._id, name: 'ken' }, { likes: ['kicking', 'punching'] }, function (err) { + should.exist(err); + + P.update({ _id: ken._id, age: 27 }, { likes: ['kicking', 'punching'] }, function (err) { + should.exist(err); + + P.update({ age: 27 }, { likes: ['kicking', 'punching'] }, function (err) { + db.close(); + should.exist(err); + }); + }); + }); + }); + }); + }, + + 'updating shard key values fails': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + P.create({ name: 'chun li', age: 19, likes: ['street fighting']}, function (err, chunli) { + should.strictEqual(null, err); + + chunli._shardval.name.should.equal('chun li'); + chunli._shardval.age.should.equal(19); + + chunli.age = 20; + chunli.save(function (err) { + /^Can't modify shard key's value field/.test(err.message).should.be.true; + + chunli._shardval.name.should.equal('chun li'); + chunli._shardval.age.should.equal(19); + + P.findById(chunli._id, function (err, chunli) { + should.strictEqual(null, err); + + chunli._shardval.name.should.equal('chun li'); + chunli._shardval.age.should.equal(19); + + chunli.name='chuuuun liiiii'; + chunli.save(function (err) { + db.close(); + /^Can't modify shard key's value field/.test(err.message).should.be.true; + }); + }); + }); + }); + } +} 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..8a37399 --- /dev/null +++ b/node_modules/mongoose/test/types.array.test.js @@ -0,0 +1,551 @@ + +/** + * 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'); + var removed = doc.numbers.splice(1, 1); + removed.should.eql([5]); + 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'); + + var removed = doc.types.splice(1, 1); + removed.length.should.eql(1); + removed[0].type.should.eql('boy'); + + 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..b275578 --- /dev/null +++ b/node_modules/mongoose/test/utils.test.js @@ -0,0 +1,196 @@ + +/** + * 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') + , MongooseBuffer = require('../lib/types/buffer') + +/** + * 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; + }, + + // gh-688 + 'deepEquals with MongooseBuffer': function () { + var str = "this is the day"; + var a = new MongooseBuffer(str); + var b = new MongooseBuffer(str); + var c = new Buffer(str); + var d = new Buffer("this is the way"); + var e = new Buffer("other length"); + + utils.deepEqual(a, b).should.be.true; + utils.deepEqual(a, c).should.be.true; + utils.deepEqual(a, d).should.be.false; + utils.deepEqual(a, e).should.be.false; + utils.deepEqual(a, []).should.be.false; + utils.deepEqual([], a).should.be.false; + } + +}; 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/request/LICENSE b/node_modules/request/LICENSE new file mode 100644 index 0000000..a4a9aee --- /dev/null +++ b/node_modules/request/LICENSE @@ -0,0 +1,55 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/node_modules/request/README.md b/node_modules/request/README.md new file mode 100644 index 0000000..4c8b7aa --- /dev/null +++ b/node_modules/request/README.md @@ -0,0 +1,285 @@ +# Request -- Simplified HTTP request method + +## Install + +
+  npm install request
+
+ +Or from source: + +
+  git clone git://github.com/mikeal/request.git 
+  cd request
+  npm link
+
+ +## Super simple to use + +Request is designed to be the simplest way possible to make http calls. It support HTTPS and follows redirects by default. + +```javascript +var request = require('request'); +request('http://www.google.com', function (error, response, body) { + if (!error && response.statusCode == 200) { + console.log(body) // Print the google web page. + } +}) +``` + +## Streaming + +You can stream any response to a file stream. + +```javascript +request('http://google.com/doodle.png').pipe(fs.createWriteStream('doodle.png')) +``` + +You can also stream a file to a PUT or POST request. This method will also check the file extension against a mapping of file extensions to content-types, in this case `application/json`, and use the proper content-type in the PUT request if one is not already provided in the headers. + +```javascript +fs.readStream('file.json').pipe(request.put('http://mysite.com/obj.json')) +``` + +Request can also pipe to itself. When doing so the content-type and content-length will be preserved in the PUT headers. + +```javascript +request.get('http://google.com/img.png').pipe(request.put('http://mysite.com/img.png')) +``` + +Now let's get fancy. + +```javascript +http.createServer(function (req, resp) { + if (req.url === '/doodle.png') { + if (req.method === 'PUT') { + req.pipe(request.put('http://mysite.com/doodle.png')) + } else if (req.method === 'GET' || req.method === 'HEAD') { + request.get('http://mysite.com/doodle.png').pipe(resp) + } + } +}) +``` + +You can also pipe() from a http.ServerRequest instance and to a http.ServerResponse instance. The HTTP method and headers will be sent as well as the entity-body data. Which means that, if you don't really care about security, you can do: + +```javascript +http.createServer(function (req, resp) { + if (req.url === '/doodle.png') { + var x = request('http://mysite.com/doodle.png') + req.pipe(x) + x.pipe(resp) + } +}) +``` + +And since pipe() returns the destination stream in node 0.5.x you can do one line proxying :) + +```javascript +req.pipe(request('http://mysite.com/doodle.png')).pipe(resp) +``` + +Also, none of this new functionality conflicts with requests previous features, it just expands them. + +```javascript +var r = request.defaults({'proxy':'http://localproxy.com'}) + +http.createServer(function (req, resp) { + if (req.url === '/doodle.png') { + r.get('http://google.com/doodle.png').pipe(resp) + } +}) +``` + +You can still use intermediate proxies, the requests will still follow HTTP forwards, etc. + +## OAuth Signing + +```javascript +// Twitter OAuth +var qs = require('querystring') + , oauth = + { callback: 'http://mysite.com/callback/' + , consumer_key: CONSUMER_KEY + , consumer_secret: CONSUMER_SECRET + } + , url = 'https://api.twitter.com/oauth/request_token' + ; +request.post({url:url, oauth:oauth}, function (e, r, body) { + // Assume by some stretch of magic you aquired the verifier + var access_token = qs.parse(body) + , oauth = + { consumer_key: CONSUMER_KEY + , consumer_secret: CONSUMER_SECRET + , token: access_token.oauth_token + , verifier: VERIFIER + , token_secret: access_token.oauth_token_secret + } + , url = 'https://api.twitter.com/oauth/access_token' + ; + request.post({url:url, oauth:oauth}, function (e, r, body) { + var perm_token = qs.parse(body) + , oauth = + { consumer_key: CONSUMER_KEY + , consumer_secret: CONSUMER_SECRET + , token: perm_token.oauth_token + , token_secret: perm_token.oauth_token_secret + } + , url = 'https://api.twitter.com/1/users/show.json?' + , params = + { screen_name: perm_token.screen_name + , user_id: perm_token.user_id + } + ; + url += qs.stringify(params) + request.get({url:url, oauth:oauth, json:true}, function (e, r, user) { + console.log(user) + }) + }) +}) +``` + + + +### request(options, callback) + +The first argument can be either a url or an options object. The only required option is uri, all others are optional. + +* `uri` || `url` - fully qualified uri or a parsed url object from url.parse() +* `method` - http method, defaults to GET +* `headers` - http headers, defaults to {} +* `body` - entity body for POST and PUT requests. Must be buffer or string. +* `json` - sets `body` but to JSON representation of value and adds `Content-type: application/json` header. +* `multipart` - (experimental) array of objects which contains their own headers and `body` attribute. Sends `multipart/related` request. See example below. +* `followRedirect` - follow HTTP 3xx responses as redirects. defaults to true. +* `maxRedirects` - the maximum number of redirects to follow, defaults to 10. +* `onResponse` - If true the callback will be fired on the "response" event instead of "end". If a function it will be called on "response" and not effect the regular semantics of the main callback on "end". +* `encoding` - Encoding to be used on `setEncoding` of response data. If set to `null`, the body is returned as a Buffer. +* `pool` - A hash object containing the agents for these requests. If omitted this request will use the global pool which is set to node's default maxSockets. +* `pool.maxSockets` - Integer containing the maximum amount of sockets in the pool. +* `timeout` - Integer containing the number of milliseconds to wait for a request to respond before aborting the request +* `proxy` - An HTTP proxy to be used. Support proxy Auth with Basic Auth the same way it's supported with the `url` parameter by embedding the auth info in the uri. +* `oauth` - Options for OAuth HMAC-SHA1 signing, see documentation above. +* `strictSSL` - Set to `true` to require that SSL certificates be valid. Note: to use your own certificate authority, you need to specify an agent that was created with that ca as an option. +* `jar` - Set to `false` if you don't want cookies to be remembered for future use or define your custom cookie jar (see examples section) + + +The callback argument gets 3 arguments. The first is an error when applicable (usually from the http.Client option not the http.ClientRequest object). The second in an http.ClientResponse object. The third is the response body String or Buffer. + +## Convenience methods + +There are also shorthand methods for different HTTP METHODs and some other conveniences. + +### request.defaults(options) + +This method returns a wrapper around the normal request API that defaults to whatever options you pass in to it. + +### request.put + +Same as request() but defaults to `method: "PUT"`. + +```javascript +request.put(url) +``` + +### request.post + +Same as request() but defaults to `method: "POST"`. + +```javascript +request.post(url) +``` + +### request.head + +Same as request() but defaults to `method: "HEAD"`. + +```javascript +request.head(url) +``` + +### request.del + +Same as request() but defaults to `method: "DELETE"`. + +```javascript +request.del(url) +``` + +### request.get + +Alias to normal request method for uniformity. + +```javascript +request.get(url) +``` +### request.cookie + +Function that creates a new cookie. + +```javascript +request.cookie('cookie_string_here') +``` +### request.jar + +Function that creates a new cookie jar. + +```javascript +request.jar() +``` + + +## Examples: + +```javascript + var request = require('request') + , rand = Math.floor(Math.random()*100000000).toString() + ; + request( + { method: 'PUT' + , uri: 'http://mikeal.iriscouch.com/testjs/' + rand + , multipart: + [ { 'content-type': 'application/json' + , body: JSON.stringify({foo: 'bar', _attachments: {'message.txt': {follows: true, length: 18, 'content_type': 'text/plain' }}}) + } + , { body: 'I am an attachment' } + ] + } + , function (error, response, body) { + if(response.statusCode == 201){ + console.log('document saved as: http://mikeal.iriscouch.com/testjs/'+ rand) + } else { + console.log('error: '+ response.statusCode) + console.log(body) + } + } + ) +``` +Cookies are enabled by default (so they can be used in subsequent requests). To disable cookies set jar to false (either in defaults or in the options sent). + +```javascript +var request = request.defaults({jar: false}) +request('http://www.google.com', function () { + request('http://images.google.com') +}) +``` + +If you to use a custom cookie jar (instead of letting request use its own global cookie jar) you do so by setting the jar default or by specifying it as an option: + +```javascript +var j = request.jar() +var request = request.defaults({jar:j}) +request('http://www.google.com', function () { + request('http://images.google.com') +}) +``` +OR + +```javascript +var j = request.jar() +var cookie = request.cookie('your_cookie_here') +j.add(cookie) +request({url: 'http://www.google.com', jar: j}, function () { + request('http://images.google.com') +}) +``` diff --git a/node_modules/request/main.js b/node_modules/request/main.js new file mode 100644 index 0000000..c64d3f7 --- /dev/null +++ b/node_modules/request/main.js @@ -0,0 +1,631 @@ +// Copyright 2010-2011 Mikeal Rogers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var http = require('http') + , https = false + , tls = false + , url = require('url') + , util = require('util') + , stream = require('stream') + , qs = require('querystring') + , mimetypes = require('./mimetypes') + , oauth = require('./oauth') + , uuid = require('./uuid') + , Cookie = require('./vendor/cookie') + , CookieJar = require('./vendor/cookie/jar') + , cookieJar = new CookieJar + ; + +if (process.logging) { + var log = process.logging('request') +} + +try { + https = require('https') +} catch (e) {} + +try { + tls = require('tls') +} catch (e) {} + +function toBase64 (str) { + return (new Buffer(str || "", "ascii")).toString("base64") +} + +// Hacky fix for pre-0.4.4 https +if (https && !https.Agent) { + https.Agent = function (options) { + http.Agent.call(this, options) + } + util.inherits(https.Agent, http.Agent) + https.Agent.prototype._getConnection = function(host, port, cb) { + var s = tls.connect(port, host, this.options, function() { + // do other checks here? + if (cb) cb() + }) + return s + } +} + +function isReadStream (rs) { + if (rs.readable && rs.path && rs.mode) { + return true + } +} + +function copy (obj) { + var o = {} + for (var i in obj) o[i] = obj[i] + return o +} + +var isUrl = /^https?:/ + +var globalPool = {} + +function Request (options) { + stream.Stream.call(this) + this.readable = true + this.writable = true + + if (typeof options === 'string') { + options = {uri:options} + } + + for (var i in options) { + this[i] = options[i] + } + if (!this.pool) this.pool = globalPool + this.dests = [] + this.__isRequestRequest = true +} +util.inherits(Request, stream.Stream) +Request.prototype.getAgent = function (host, port) { + if (!this.pool[host+':'+port]) { + this.pool[host+':'+port] = new this.httpModule.Agent({host:host, port:port}) + } + return this.pool[host+':'+port] +} +Request.prototype.request = function () { + var self = this + + // Protect against double callback + if (!self._callback && self.callback) { + self._callback = self.callback + self.callback = function () { + if (self._callbackCalled) return // Print a warning maybe? + self._callback.apply(self, arguments) + self._callbackCalled = true + } + } + + if (self.url) { + // People use this property instead all the time so why not just support it. + self.uri = self.url + delete self.url + } + + if (!self.uri) { + throw new Error("options.uri is a required argument") + } else { + if (typeof self.uri == "string") self.uri = url.parse(self.uri) + } + if (self.proxy) { + if (typeof self.proxy == 'string') self.proxy = url.parse(self.proxy) + } + + self._redirectsFollowed = self._redirectsFollowed || 0 + self.maxRedirects = (self.maxRedirects !== undefined) ? self.maxRedirects : 10 + self.followRedirect = (self.followRedirect !== undefined) ? self.followRedirect : true + if (self.followRedirect) + self.redirects = self.redirects || [] + + self.headers = self.headers ? copy(self.headers) : {} + + var setHost = false + if (!self.headers.host) { + self.headers.host = self.uri.hostname + if (self.uri.port) { + if ( !(self.uri.port === 80 && self.uri.protocol === 'http:') && + !(self.uri.port === 443 && self.uri.protocol === 'https:') ) + self.headers.host += (':'+self.uri.port) + } + setHost = true + } + + if (self.jar === false) { + // disable cookies + var cookies = false; + self._disableCookies = true; + } else if (self.jar) { + // fetch cookie from the user defined cookie jar + var cookies = self.jar.get({ url: self.uri.href }) + } else { + // fetch cookie from the global cookie jar + var cookies = cookieJar.get({ url: self.uri.href }) + } + if (cookies) { + var cookieString = cookies.map(function (c) { + return c.name + "=" + c.value; + }).join("; "); + + self.headers.Cookie = cookieString; + } + + if (!self.uri.pathname) {self.uri.pathname = '/'} + if (!self.uri.port) { + if (self.uri.protocol == 'http:') {self.uri.port = 80} + else if (self.uri.protocol == 'https:') {self.uri.port = 443} + } + + if (self.proxy) { + self.port = self.proxy.port + self.host = self.proxy.hostname + } else { + self.port = self.uri.port + self.host = self.uri.hostname + } + + if (self.onResponse === true) { + self.onResponse = self.callback + delete self.callback + } + + var clientErrorHandler = function (error) { + if (setHost) delete self.headers.host + if (self.timeout && self.timeoutTimer) clearTimeout(self.timeoutTimer) + self.emit('error', error) + } + if (self.onResponse) self.on('error', function (e) {self.onResponse(e)}) + if (self.callback) self.on('error', function (e) {self.callback(e)}) + + if (self.form) { + self.headers['content-type'] = 'application/x-www-form-urlencoded; charset=utf-8' + self.body = qs.stringify(self.form).toString('utf8') + } + + if (self.oauth) { + var form + if (self.headers['content-type'] && + self.headers['content-type'].slice(0, 'application/x-www-form-urlencoded'.length) === + 'application/x-www-form-urlencoded' + ) { + form = qs.parse(self.body) + } + if (self.uri.query) { + form = qs.parse(self.uri.query) + } + if (!form) form = {} + var oa = {} + for (var i in form) oa[i] = form[i] + for (var i in self.oauth) oa['oauth_'+i] = self.oauth[i] + if (!oa.oauth_version) oa.oauth_version = '1.0' + if (!oa.oauth_timestamp) oa.oauth_timestamp = Math.floor( (new Date()).getTime() / 1000 ).toString() + if (!oa.oauth_nonce) oa.oauth_nonce = uuid().replace(/-/g, '') + + oa.oauth_signature_method = 'HMAC-SHA1' + + var consumer_secret = oa.oauth_consumer_secret + delete oa.oauth_consumer_secret + var token_secret = oa.oauth_token_secret + delete oa.oauth_token_secret + + var baseurl = self.uri.protocol + '//' + self.uri.host + self.uri.pathname + var signature = oauth.hmacsign(self.method, baseurl, oa, consumer_secret, token_secret) + + // oa.oauth_signature = signature + for (var i in form) { + if ( i.slice(0, 'oauth_') in self.oauth) { + // skip + } else { + delete oa['oauth_'+i] + } + } + self.headers.authorization = + 'OAuth '+Object.keys(oa).sort().map(function (i) {return i+'="'+oauth.rfc3986(oa[i])+'"'}).join(',') + self.headers.authorization += ',oauth_signature="'+oauth.rfc3986(signature)+'"' + } + + if (self.uri.auth && !self.headers.authorization) { + self.headers.authorization = "Basic " + toBase64(self.uri.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':')) + } + if (self.proxy && self.proxy.auth && !self.headers['proxy-authorization']) { + self.headers['proxy-authorization'] = "Basic " + toBase64(self.proxy.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':')) + } + + if (self.uri.path) { + self.path = self.uri.path + } else { + self.path = self.uri.pathname + (self.uri.search || "") + } + + if (self.path.length === 0) self.path = '/' + + if (self.proxy) self.path = (self.uri.protocol + '//' + self.uri.host + self.path) + + if (self.json) { + self.headers['content-type'] = 'application/json' + if (typeof self.json === 'boolean') { + if (typeof self.body === 'object') self.body = JSON.stringify(self.body) + } else { + self.body = JSON.stringify(self.json) + } + + } else if (self.multipart) { + self.body = []; + self.headers['content-type'] = 'multipart/related;boundary="frontier"' + if (!self.multipart.forEach) throw new Error('Argument error, options.multipart.') + + self.multipart.forEach(function (part) { + var body = part.body + if(!body) throw Error('Body attribute missing in multipart.') + delete part.body + var preamble = '--frontier\r\n' + Object.keys(part).forEach(function(key){ + preamble += key + ': ' + part[key] + '\r\n' + }) + preamble += '\r\n'; + self.body.push(new Buffer(preamble)); + self.body.push(new Buffer(body)); + self.body.push(new Buffer('\r\n')); + }) + self.body.push(new Buffer('--frontier--')); + } + + if (self.body) { + var length = 0; + if (!Buffer.isBuffer(self.body)) { + if (Array.isArray(self.body)) { + for (var i = 0; i < self.body.length; i++) { + length += self.body[i].length; + } + } else { + self.body = new Buffer(self.body) + length = self.body.length; + } + } else { + length = self.body.length; + } + if (length) { + self.headers['content-length'] = length; + } else { + throw new Error('Argument error, options.body.') + } + } + + var protocol = self.proxy ? self.proxy.protocol : self.uri.protocol + , defaultModules = {'http:':http, 'https:':https} + , httpModules = self.httpModules || {} + + self.httpModule = httpModules[protocol] || defaultModules[protocol] + + if (!self.httpModule) throw new Error("Invalid protocol") + + if (self.pool === false) { + self.agent = false + } else { + if (self.maxSockets) { + // Don't use our pooling if node has the refactored client + self.agent = self.httpModule.globalAgent || self.getAgent(self.host, self.port) + self.agent.maxSockets = self.maxSockets + } + if (self.pool.maxSockets) { + // Don't use our pooling if node has the refactored client + self.agent = self.httpModule.globalAgent || self.getAgent(self.host, self.port) + self.agent.maxSockets = self.pool.maxSockets + } + } + + self.start = function () { + self._started = true + self.method = self.method || 'GET' + if (log) log('%method %uri', self) + self.req = self.httpModule.request(self, function (response) { + self.response = response + response.request = self + + if (self.httpModule === https && + self.strictSSL && + !response.client.authorized) { + var sslErr = response.client.authorizationError + self.emit('error', new Error('SSL Error: '+ sslErr)) + return + } + + if (setHost) delete self.headers.host + if (self.timeout && self.timeoutTimer) clearTimeout(self.timeoutTimer) + + if (response.headers['set-cookie'] && (!self._disableCookies)) { + response.headers['set-cookie'].forEach(function(cookie) { + if (self.jar) { + // custom defined jar + self.jar.add(new Cookie(cookie)); + } + else { + // add to the global cookie jar if user don't define his own + cookieJar.add(new Cookie(cookie)); + } + }); + } + + if (response.statusCode >= 300 && + response.statusCode < 400 && + self.followRedirect && + self.method !== 'PUT' && + self.method !== 'POST' && + response.headers.location) { + if (self._redirectsFollowed >= self.maxRedirects) { + self.emit('error', new Error("Exceeded maxRedirects. Probably stuck in a redirect loop.")) + return + } + self._redirectsFollowed += 1 + + if (!isUrl.test(response.headers.location)) { + response.headers.location = url.resolve(self.uri.href, response.headers.location) + } + self.uri = response.headers.location + self.redirects.push( { statusCode : response.statusCode, + redirectUri: response.headers.location }) + delete self.req + delete self.agent + delete self._started + if (self.headers) { + delete self.headers.host + } + if (log) log('Redirect to %uri', self) + request(self, self.callback) + return // Ignore the rest of the response + } else { + self._redirectsFollowed = self._redirectsFollowed || 0 + // Be a good stream and emit end when the response is finished. + // Hack to emit end on close because of a core bug that never fires end + response.on('close', function () { + if (!self._ended) self.response.emit('end') + }) + + if (self.encoding) { + if (self.dests.length !== 0) { + console.error("Ingoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.") + } else { + response.setEncoding(self.encoding) + } + } + + self.pipeDest = function (dest) { + if (dest.headers) { + dest.headers['content-type'] = response.headers['content-type'] + if (response.headers['content-length']) { + dest.headers['content-length'] = response.headers['content-length'] + } + } + if (dest.setHeader) { + for (var i in response.headers) { + dest.setHeader(i, response.headers[i]) + } + dest.statusCode = response.statusCode + } + if (self.pipefilter) self.pipefilter(response, dest) + } + + self.dests.forEach(function (dest) { + self.pipeDest(dest) + }) + + response.on("data", function (chunk) { + self._destdata = true + self.emit("data", chunk) + }) + response.on("end", function (chunk) { + self._ended = true + self.emit("end", chunk) + }) + response.on("close", function () {self.emit("close")}) + + if (log) log('Response %statusCode', response) + self.emit('response', response) + + if (self.onResponse) { + self.onResponse(null, response) + } + if (self.callback) { + var buffer = [] + var bodyLen = 0 + self.on("data", function (chunk) { + buffer.push(chunk) + bodyLen += chunk.length + }) + self.on("end", function () { + if (buffer.length && Buffer.isBuffer(buffer[0])) { + var body = new Buffer(bodyLen) + var i = 0 + buffer.forEach(function (chunk) { + chunk.copy(body, i, 0, chunk.length) + i += chunk.length + }) + if (self.encoding === null) { + response.body = body + } else { + response.body = body.toString() + } + } else if (buffer.length) { + response.body = buffer.join('') + } + + if (self.json) { + try { + response.body = JSON.parse(response.body) + } catch (e) {} + } + + self.callback(null, response, response.body) + }) + } + } + }) + + if (self.timeout) { + self.timeoutTimer = setTimeout(function() { + self.req.abort() + var e = new Error("ETIMEDOUT") + e.code = "ETIMEDOUT" + self.emit("error", e) + }, self.timeout) + } + + self.req.on('error', clientErrorHandler) + } + + self.once('pipe', function (src) { + if (self.ntick) throw new Error("You cannot pipe to this stream after the first nextTick() after creation of the request stream.") + self.src = src + if (isReadStream(src)) { + if (!self.headers['content-type'] && !self.headers['Content-Type']) + self.headers['content-type'] = mimetypes.lookup(src.path.slice(src.path.lastIndexOf('.')+1)) + } else { + if (src.headers) { + for (var i in src.headers) { + if (!self.headers[i]) { + self.headers[i] = src.headers[i] + } + } + } + if (src.method && !self.method) { + self.method = src.method + } + } + + self.on('pipe', function () { + console.error("You have already piped to this stream. Pipeing twice is likely to break the request.") + }) + }) + + process.nextTick(function () { + if (self.body) { + if (Array.isArray(self.body)) { + self.body.forEach(function(part) { + self.write(part); + }); + } else { + self.write(self.body) + } + self.end() + } else if (self.requestBodyStream) { + console.warn("options.requestBodyStream is deprecated, please pass the request object to stream.pipe.") + self.requestBodyStream.pipe(self) + } else if (!self.src) { + self.headers['content-length'] = 0 + self.end() + } + self.ntick = true + }) +} +Request.prototype.pipe = function (dest) { + if (this.response) { + if (this._destdata) { + throw new Error("You cannot pipe after data has been emitted from the response.") + } else if (this._ended) { + throw new Error("You cannot pipe after the response has been ended.") + } else { + stream.Stream.prototype.pipe.call(this, dest) + this.pipeDest(dest) + return dest + } + } else { + this.dests.push(dest) + stream.Stream.prototype.pipe.call(this, dest) + return dest + } +} +Request.prototype.write = function () { + if (!this._started) this.start() + if (!this.req) throw new Error("This request has been piped before http.request() was called.") + this.req.write.apply(this.req, arguments) +} +Request.prototype.end = function () { + if (!this._started) this.start() + if (!this.req) throw new Error("This request has been piped before http.request() was called.") + this.req.end.apply(this.req, arguments) +} +Request.prototype.pause = function () { + if (!this.response) throw new Error("This request has been piped before http.request() was called.") + this.response.pause.apply(this.response, arguments) +} +Request.prototype.resume = function () { + if (!this.response) throw new Error("This request has been piped before http.request() was called.") + this.response.resume.apply(this.response, arguments) +} + +function request (options, callback) { + if (typeof options === 'string') options = {uri:options} + if (callback) options.callback = callback + var r = new Request(options) + r.request() + return r +} + +module.exports = request + +request.defaults = function (options) { + var def = function (method) { + var d = function (opts, callback) { + if (typeof opts === 'string') opts = {uri:opts} + for (var i in options) { + if (opts[i] === undefined) opts[i] = options[i] + } + return method(opts, callback) + } + return d + } + var de = def(request) + de.get = def(request.get) + de.post = def(request.post) + de.put = def(request.put) + de.head = def(request.head) + de.del = def(request.del) + de.cookie = def(request.cookie) + de.jar = def(request.jar) + return de +} + +request.get = request +request.post = function (options, callback) { + if (typeof options === 'string') options = {uri:options} + options.method = 'POST' + return request(options, callback) +} +request.put = function (options, callback) { + if (typeof options === 'string') options = {uri:options} + options.method = 'PUT' + return request(options, callback) +} +request.head = function (options, callback) { + if (typeof options === 'string') options = {uri:options} + options.method = 'HEAD' + if (options.body || options.requestBodyStream || options.json || options.multipart) { + throw new Error("HTTP HEAD requests MUST NOT include a request body.") + } + return request(options, callback) +} +request.del = function (options, callback) { + if (typeof options === 'string') options = {uri:options} + options.method = 'DELETE' + return request(options, callback) +} +request.jar = function () { + return new CookieJar +} +request.cookie = function (str) { + if (typeof str !== 'string') throw new Error("The cookie function only accepts STRING as param") + return new Cookie(str) +} diff --git a/node_modules/request/mimetypes.js b/node_modules/request/mimetypes.js new file mode 100644 index 0000000..8691006 --- /dev/null +++ b/node_modules/request/mimetypes.js @@ -0,0 +1,146 @@ +// from http://github.com/felixge/node-paperboy +exports.types = { + "aiff":"audio/x-aiff", + "arj":"application/x-arj-compressed", + "asf":"video/x-ms-asf", + "asx":"video/x-ms-asx", + "au":"audio/ulaw", + "avi":"video/x-msvideo", + "bcpio":"application/x-bcpio", + "ccad":"application/clariscad", + "cod":"application/vnd.rim.cod", + "com":"application/x-msdos-program", + "cpio":"application/x-cpio", + "cpt":"application/mac-compactpro", + "csh":"application/x-csh", + "css":"text/css", + "deb":"application/x-debian-package", + "dl":"video/dl", + "doc":"application/msword", + "drw":"application/drafting", + "dvi":"application/x-dvi", + "dwg":"application/acad", + "dxf":"application/dxf", + "dxr":"application/x-director", + "etx":"text/x-setext", + "ez":"application/andrew-inset", + "fli":"video/x-fli", + "flv":"video/x-flv", + "gif":"image/gif", + "gl":"video/gl", + "gtar":"application/x-gtar", + "gz":"application/x-gzip", + "hdf":"application/x-hdf", + "hqx":"application/mac-binhex40", + "html":"text/html", + "ice":"x-conference/x-cooltalk", + "ico":"image/x-icon", + "ief":"image/ief", + "igs":"model/iges", + "ips":"application/x-ipscript", + "ipx":"application/x-ipix", + "jad":"text/vnd.sun.j2me.app-descriptor", + "jar":"application/java-archive", + "jpeg":"image/jpeg", + "jpg":"image/jpeg", + "js":"text/javascript", + "json":"application/json", + "latex":"application/x-latex", + "lsp":"application/x-lisp", + "lzh":"application/octet-stream", + "m":"text/plain", + "m3u":"audio/x-mpegurl", + "man":"application/x-troff-man", + "me":"application/x-troff-me", + "midi":"audio/midi", + "mif":"application/x-mif", + "mime":"www/mime", + "movie":"video/x-sgi-movie", + "mustache":"text/plain", + "mp4":"video/mp4", + "mpg":"video/mpeg", + "mpga":"audio/mpeg", + "ms":"application/x-troff-ms", + "nc":"application/x-netcdf", + "oda":"application/oda", + "ogm":"application/ogg", + "pbm":"image/x-portable-bitmap", + "pdf":"application/pdf", + "pgm":"image/x-portable-graymap", + "pgn":"application/x-chess-pgn", + "pgp":"application/pgp", + "pm":"application/x-perl", + "png":"image/png", + "pnm":"image/x-portable-anymap", + "ppm":"image/x-portable-pixmap", + "ppz":"application/vnd.ms-powerpoint", + "pre":"application/x-freelance", + "prt":"application/pro_eng", + "ps":"application/postscript", + "qt":"video/quicktime", + "ra":"audio/x-realaudio", + "rar":"application/x-rar-compressed", + "ras":"image/x-cmu-raster", + "rgb":"image/x-rgb", + "rm":"audio/x-pn-realaudio", + "rpm":"audio/x-pn-realaudio-plugin", + "rtf":"text/rtf", + "rtx":"text/richtext", + "scm":"application/x-lotusscreencam", + "set":"application/set", + "sgml":"text/sgml", + "sh":"application/x-sh", + "shar":"application/x-shar", + "silo":"model/mesh", + "sit":"application/x-stuffit", + "skt":"application/x-koan", + "smil":"application/smil", + "snd":"audio/basic", + "sol":"application/solids", + "spl":"application/x-futuresplash", + "src":"application/x-wais-source", + "stl":"application/SLA", + "stp":"application/STEP", + "sv4cpio":"application/x-sv4cpio", + "sv4crc":"application/x-sv4crc", + "svg":"image/svg+xml", + "swf":"application/x-shockwave-flash", + "tar":"application/x-tar", + "tcl":"application/x-tcl", + "tex":"application/x-tex", + "texinfo":"application/x-texinfo", + "tgz":"application/x-tar-gz", + "tiff":"image/tiff", + "tr":"application/x-troff", + "tsi":"audio/TSP-audio", + "tsp":"application/dsptype", + "tsv":"text/tab-separated-values", + "unv":"application/i-deas", + "ustar":"application/x-ustar", + "vcd":"application/x-cdlink", + "vda":"application/vda", + "vivo":"video/vnd.vivo", + "vrm":"x-world/x-vrml", + "wav":"audio/x-wav", + "wax":"audio/x-ms-wax", + "wma":"audio/x-ms-wma", + "wmv":"video/x-ms-wmv", + "wmx":"video/x-ms-wmx", + "wrl":"model/vrml", + "wvx":"video/x-ms-wvx", + "xbm":"image/x-xbitmap", + "xlw":"application/vnd.ms-excel", + "xml":"text/xml", + "xpm":"image/x-xpixmap", + "xwd":"image/x-xwindowdump", + "xyz":"chemical/x-pdb", + "zip":"application/zip", +}; + +exports.lookup = function(ext, defaultType) { + defaultType = defaultType || 'application/octet-stream'; + + return (ext in exports.types) + ? exports.types[ext] + : defaultType; +}; \ No newline at end of file diff --git a/node_modules/request/oauth.js b/node_modules/request/oauth.js new file mode 100644 index 0000000..25db669 --- /dev/null +++ b/node_modules/request/oauth.js @@ -0,0 +1,34 @@ +var crypto = require('crypto') + , qs = require('querystring') + ; + +function sha1 (key, body) { + return crypto.createHmac('sha1', key).update(body).digest('base64') +} + +function rfc3986 (str) { + return encodeURIComponent(str) + .replace('!','%21') + .replace('*','%2A') + .replace('(','%28') + .replace(')','%29') + .replace("'",'%27') + ; +} + +function hmacsign (httpMethod, base_uri, params, consumer_secret, token_secret, body) { + // adapted from https://dev.twitter.com/docs/auth/oauth + var base = + httpMethod + "&" + + encodeURIComponent( base_uri ) + "&" + + Object.keys(params).sort().map(function (i) { + // big WTF here with the escape + encoding but it's what twitter wants + return escape(rfc3986(i)) + "%3D" + escape(rfc3986(params[i])) + }).join("%26") + var key = consumer_secret + '&' + if (token_secret) key += token_secret + return sha1(key, base) +} + +exports.hmacsign = hmacsign +exports.rfc3986 = rfc3986 \ No newline at end of file diff --git a/node_modules/request/package.json b/node_modules/request/package.json new file mode 100644 index 0000000..0600c34 --- /dev/null +++ b/node_modules/request/package.json @@ -0,0 +1,41 @@ +{ + "name": "request", + "description": "Simplified HTTP request client.", + "tags": [ + "http", + "simple", + "util", + "utility" + ], + "version": "2.9.0", + "author": { + "name": "Mikeal Rogers", + "email": "mikeal.rogers@gmail.com" + }, + "repository": { + "type": "git", + "url": "git://github.com/mikeal/request.git" + }, + "bugs": { + "url": "http://github.com/mikeal/request/issues" + }, + "engines": [ + "node >= 0.3.6" + ], + "main": "./main", + "scripts": { + "test": "bash tests/run.sh" + }, + "_id": "request@2.9.0", + "dependencies": {}, + "devDependencies": {}, + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "187440243335d8f31474ed484096af54482dbabd" + }, + "_from": "request@2.9.0" +} diff --git a/node_modules/request/tests/googledoodle.png b/node_modules/request/tests/googledoodle.png new file mode 100644 index 0000000..f80c9c5 Binary files /dev/null and b/node_modules/request/tests/googledoodle.png differ diff --git a/node_modules/request/tests/run.sh b/node_modules/request/tests/run.sh new file mode 100755 index 0000000..57d0f64 --- /dev/null +++ b/node_modules/request/tests/run.sh @@ -0,0 +1,6 @@ +FAILS=0 +for i in tests/test-*.js; do + echo $i + node $i || let FAILS++ +done +exit $FAILS diff --git a/node_modules/request/tests/server.js b/node_modules/request/tests/server.js new file mode 100644 index 0000000..2e1889f --- /dev/null +++ b/node_modules/request/tests/server.js @@ -0,0 +1,75 @@ +var fs = require('fs') + , http = require('http') + , path = require('path') + , https = require('https') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + ; + +exports.createServer = function (port) { + port = port || 6767 + var s = http.createServer(function (req, resp) { + s.emit(req.url, req, resp); + }) + s.port = port + s.url = 'http://localhost:'+port + return s; +} + +exports.createSSLServer = function(port) { + port = port || 16767 + + var options = { 'key' : fs.readFileSync(path.join(__dirname, 'ssl', 'test.key')) + , 'cert': fs.readFileSync(path.join(__dirname, 'ssl', 'test.crt')) + } + + var s = https.createServer(options, function (req, resp) { + s.emit(req.url, req, resp); + }) + s.port = port + s.url = 'https://localhost:'+port + return s; +} + +exports.createPostStream = function (text) { + var postStream = new stream.Stream(); + postStream.writeable = true; + postStream.readable = true; + setTimeout(function () {postStream.emit('data', new Buffer(text)); postStream.emit('end')}, 0); + return postStream; +} +exports.createPostValidator = function (text) { + var l = function (req, resp) { + var r = ''; + req.on('data', function (chunk) {r += chunk}) + req.on('end', function () { + if (r !== text) console.log(r, text); + assert.equal(r, text) + resp.writeHead(200, {'content-type':'text/plain'}) + resp.write('OK') + resp.end() + }) + } + return l; +} +exports.createGetResponse = function (text, contentType) { + var l = function (req, resp) { + contentType = contentType || 'text/plain' + resp.writeHead(200, {'content-type':contentType}) + resp.write(text) + resp.end() + } + return l; +} +exports.createChunkResponse = function (chunks, contentType) { + var l = function (req, resp) { + contentType = contentType || 'text/plain' + resp.writeHead(200, {'content-type':contentType}) + chunks.forEach(function (chunk) { + resp.write(chunk) + }) + resp.end() + } + return l; +} diff --git a/node_modules/request/tests/ssl/test.crt b/node_modules/request/tests/ssl/test.crt new file mode 100644 index 0000000..b357f86 --- /dev/null +++ b/node_modules/request/tests/ssl/test.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICQzCCAawCCQCO/XWtRFck1jANBgkqhkiG9w0BAQUFADBmMQswCQYDVQQGEwJU +SDEQMA4GA1UECBMHQmFuZ2tvazEOMAwGA1UEBxMFU2lsb20xGzAZBgNVBAoTElRo +ZSBSZXF1ZXN0IE1vZHVsZTEYMBYGA1UEAxMPcmVxdWVzdC5leGFtcGxlMB4XDTEx +MTIwMzAyMjkyM1oXDTIxMTEzMDAyMjkyM1owZjELMAkGA1UEBhMCVEgxEDAOBgNV +BAgTB0Jhbmdrb2sxDjAMBgNVBAcTBVNpbG9tMRswGQYDVQQKExJUaGUgUmVxdWVz +dCBNb2R1bGUxGDAWBgNVBAMTD3JlcXVlc3QuZXhhbXBsZTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAwmctddZqlA48+NXs0yOy92DijcQV1jf87zMiYAIlNUto +wghVbTWgJU5r0pdKrD16AptnWJTzKanhItEX8XCCPgsNkq1afgTtJP7rNkwu3xcj +eIMkhJg/ay4ZnkbnhYdsii5VTU5prix6AqWRAhbkBgoA+iVyHyof8wvZyKBoFTMC +AwEAATANBgkqhkiG9w0BAQUFAAOBgQB6BybMJbpeiABgihDfEVBcAjDoQ8gUMgwV +l4NulugfKTDmArqnR9aPd4ET5jX5dkMP4bwCHYsvrcYDeWEQy7x5WWuylOdKhua4 +L4cEi2uDCjqEErIG3cc1MCOk6Cl6Ld6tkIzQSf953qfdEACRytOeUqLNQcrXrqeE +c7U8F6MWLQ== +-----END CERTIFICATE----- diff --git a/node_modules/request/tests/ssl/test.key b/node_modules/request/tests/ssl/test.key new file mode 100644 index 0000000..b85810d --- /dev/null +++ b/node_modules/request/tests/ssl/test.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDCZy111mqUDjz41ezTI7L3YOKNxBXWN/zvMyJgAiU1S2jCCFVt +NaAlTmvSl0qsPXoCm2dYlPMpqeEi0RfxcII+Cw2SrVp+BO0k/us2TC7fFyN4gySE +mD9rLhmeRueFh2yKLlVNTmmuLHoCpZECFuQGCgD6JXIfKh/zC9nIoGgVMwIDAQAB +AoGBALXFwfUf8vHTSmGlrdZS2AGFPvEtuvldyoxi9K5u8xmdFCvxnOcLsF2RsTHt +Mu5QYWhUpNJoG+IGLTPf7RJdj/kNtEs7xXqWy4jR36kt5z5MJzqiK+QIgiO9UFWZ +fjUb6oeDnTIJA9YFBdYi97MDuL89iU/UK3LkJN3hd4rciSbpAkEA+MCkowF5kSFb +rkOTBYBXZfiAG78itDXN6DXmqb9XYY+YBh3BiQM28oxCeQYyFy6pk/nstnd4TXk6 +V/ryA2g5NwJBAMgRKTY9KvxJWbESeMEFe2iBIV0c26/72Amgi7ZKUCLukLfD4tLF ++WSZdmTbbqI1079YtwaiOVfiLm45Q/3B0eUCQAaQ/0eWSGE+Yi8tdXoVszjr4GXb +G81qBi91DMu6U1It+jNfIba+MPsiHLcZJMVb4/oWBNukN7bD1nhwFWdlnu0CQQCf +Is9WHkdvz2RxbZDxb8verz/7kXXJQJhx5+rZf7jIYFxqX3yvTNv3wf2jcctJaWlZ +fVZwB193YSivcgt778xlAkEAprYUz3jczjF5r2hrgbizPzPDR94tM5BTO3ki2v3w +kbf+j2g7FNAx6kZiVN8XwfLc8xEeUGiPKwtq3ddPDFh17w== +-----END RSA PRIVATE KEY----- diff --git a/node_modules/request/tests/test-body.js b/node_modules/request/tests/test-body.js new file mode 100644 index 0000000..9d2e188 --- /dev/null +++ b/node_modules/request/tests/test-body.js @@ -0,0 +1,95 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , request = require('../main.js') + ; + +var s = server.createServer(); + +var tests = + { testGet : + { resp : server.createGetResponse("TESTING!") + , expectBody: "TESTING!" + } + , testGetChunkBreak : + { resp : server.createChunkResponse( + [ new Buffer([239]) + , new Buffer([163]) + , new Buffer([191]) + , new Buffer([206]) + , new Buffer([169]) + , new Buffer([226]) + , new Buffer([152]) + , new Buffer([131]) + ]) + , expectBody: "Ω☃" + } + , testGetBuffer : + { resp : server.createGetResponse(new Buffer("TESTING!")) + , encoding: null + , expectBody: new Buffer("TESTING!") + } + , testGetJSON : + { resp : server.createGetResponse('{"test":true}', 'application/json') + , json : true + , expectBody: {"test":true} + } + , testPutString : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : "PUTTINGDATA" + } + , testPutBuffer : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : new Buffer("PUTTINGDATA") + } + , testPutJSON : + { resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) + , method: "PUT" + , json: {foo: 'bar'} + } + , testPutMultipart : + { resp: server.createPostValidator( + '--frontier\r\n' + + 'content-type: text/html\r\n' + + '\r\n' + + 'Oh hi.' + + '\r\n--frontier\r\n\r\n' + + 'Oh hi.' + + '\r\n--frontier--' + ) + , method: "PUT" + , multipart: + [ {'content-type': 'text/html', 'body': 'Oh hi.'} + , {'body': 'Oh hi.'} + ] + } + } + +s.listen(s.port, function () { + + var counter = 0 + + for (i in tests) { + (function () { + var test = tests[i] + s.on('/'+i, test.resp) + test.uri = s.url + '/' + i + request(test, function (err, resp, body) { + if (err) throw err + if (test.expectBody) { + assert.deepEqual(test.expectBody, body) + } + counter = counter - 1; + if (counter === 0) { + console.log(Object.keys(tests).length+" tests passed.") + s.close() + } + }) + counter++ + })() + } +}) + diff --git a/node_modules/request/tests/test-cookie.js b/node_modules/request/tests/test-cookie.js new file mode 100644 index 0000000..f17cfb3 --- /dev/null +++ b/node_modules/request/tests/test-cookie.js @@ -0,0 +1,29 @@ +var Cookie = require('../vendor/cookie') + , assert = require('assert'); + +var str = 'sid="s543qactge.wKE61E01Bs%2BKhzmxrwrnug="; path=/; httpOnly; expires=Sat, 04 Dec 2010 23:27:28 GMT'; +var cookie = new Cookie(str); + +// test .toString() +assert.equal(cookie.toString(), str); + +// test .path +assert.equal(cookie.path, '/'); + +// test .httpOnly +assert.equal(cookie.httpOnly, true); + +// test .name +assert.equal(cookie.name, 'sid'); + +// test .value +assert.equal(cookie.value, '"s543qactge.wKE61E01Bs%2BKhzmxrwrnug="'); + +// test .expires +assert.equal(cookie.expires instanceof Date, true); + +// test .path default +var cookie = new Cookie('foo=bar', { url: 'http://foo.com/bar' }); +assert.equal(cookie.path, '/bar'); + +console.log('All tests passed'); diff --git a/node_modules/request/tests/test-cookiejar.js b/node_modules/request/tests/test-cookiejar.js new file mode 100644 index 0000000..76fcd71 --- /dev/null +++ b/node_modules/request/tests/test-cookiejar.js @@ -0,0 +1,90 @@ +var Cookie = require('../vendor/cookie') + , Jar = require('../vendor/cookie/jar') + , assert = require('assert'); + +function expires(ms) { + return new Date(Date.now() + ms).toUTCString(); +} + +// test .get() expiration +(function() { + var jar = new Jar; + var cookie = new Cookie('sid=1234; path=/; expires=' + expires(1000)); + jar.add(cookie); + setTimeout(function(){ + var cookies = jar.get({ url: 'http://foo.com/foo' }); + assert.equal(cookies.length, 1); + assert.equal(cookies[0], cookie); + setTimeout(function(){ + var cookies = jar.get({ url: 'http://foo.com/foo' }); + assert.equal(cookies.length, 0); + }, 1000); + }, 5); +})(); + +// test .get() path support +(function() { + var jar = new Jar; + var a = new Cookie('sid=1234; path=/'); + var b = new Cookie('sid=1111; path=/foo/bar'); + var c = new Cookie('sid=2222; path=/'); + jar.add(a); + jar.add(b); + jar.add(c); + + // should remove the duplicates + assert.equal(jar.cookies.length, 2); + + // same name, same path, latter prevails + var cookies = jar.get({ url: 'http://foo.com/' }); + assert.equal(cookies.length, 1); + assert.equal(cookies[0], c); + + // same name, diff path, path specifity prevails, latter prevails + var cookies = jar.get({ url: 'http://foo.com/foo/bar' }); + assert.equal(cookies.length, 1); + assert.equal(cookies[0], b); + + var jar = new Jar; + var a = new Cookie('sid=1111; path=/foo/bar'); + var b = new Cookie('sid=1234; path=/'); + jar.add(a); + jar.add(b); + + var cookies = jar.get({ url: 'http://foo.com/foo/bar' }); + assert.equal(cookies.length, 1); + assert.equal(cookies[0], a); + + var cookies = jar.get({ url: 'http://foo.com/' }); + assert.equal(cookies.length, 1); + assert.equal(cookies[0], b); + + var jar = new Jar; + var a = new Cookie('sid=1111; path=/foo/bar'); + var b = new Cookie('sid=3333; path=/foo/bar'); + var c = new Cookie('pid=3333; path=/foo/bar'); + var d = new Cookie('sid=2222; path=/foo/'); + var e = new Cookie('sid=1234; path=/'); + jar.add(a); + jar.add(b); + jar.add(c); + jar.add(d); + jar.add(e); + + var cookies = jar.get({ url: 'http://foo.com/foo/bar' }); + assert.equal(cookies.length, 2); + assert.equal(cookies[0], b); + assert.equal(cookies[1], c); + + var cookies = jar.get({ url: 'http://foo.com/foo/' }); + assert.equal(cookies.length, 1); + assert.equal(cookies[0], d); + + var cookies = jar.get({ url: 'http://foo.com/' }); + assert.equal(cookies.length, 1); + assert.equal(cookies[0], e); +})(); + +setTimeout(function() { + console.log('All tests passed'); +}, 1200); diff --git a/node_modules/request/tests/test-errors.js b/node_modules/request/tests/test-errors.js new file mode 100644 index 0000000..a7db1f7 --- /dev/null +++ b/node_modules/request/tests/test-errors.js @@ -0,0 +1,30 @@ +var server = require('./server') + , events = require('events') + , assert = require('assert') + , request = require('../main.js') + ; + +var local = 'http://localhost:8888/asdf' + +try { + request({uri:local, body:{}}) + assert.fail("Should have throw") +} catch(e) { + assert.equal(e.message, 'Argument error, options.body.') +} + +try { + request({uri:local, multipart: 'foo'}) + assert.fail("Should have throw") +} catch(e) { + assert.equal(e.message, 'Argument error, options.multipart.') +} + +try { + request({uri:local, multipart: [{}]}) + assert.fail("Should have throw") +} catch(e) { + assert.equal(e.message, 'Body attribute missing in multipart.') +} + +console.log("All tests passed.") diff --git a/node_modules/request/tests/test-httpModule.js b/node_modules/request/tests/test-httpModule.js new file mode 100644 index 0000000..6d8c83f --- /dev/null +++ b/node_modules/request/tests/test-httpModule.js @@ -0,0 +1,94 @@ +var http = require('http') + , https = require('https') + , server = require('./server') + , assert = require('assert') + , request = require('../main.js') + + +var faux_requests_made = {'http':0, 'https':0} +function wrap_request(name, module) { + // Just like the http or https module, but note when a request is made. + var wrapped = {} + Object.keys(module).forEach(function(key) { + var value = module[key]; + + if(key != 'request') + wrapped[key] = value; + else + wrapped[key] = function(options, callback) { + faux_requests_made[name] += 1 + return value.apply(this, arguments) + } + }) + + return wrapped; +} + + +var faux_http = wrap_request('http', http) + , faux_https = wrap_request('https', https) + , plain_server = server.createServer() + , https_server = server.createSSLServer() + + +plain_server.listen(plain_server.port, function() { + plain_server.on('/plain', function (req, res) { + res.writeHead(200) + res.end('plain') + }) + plain_server.on('/to_https', function (req, res) { + res.writeHead(301, {'location':'https://localhost:'+https_server.port + '/https'}) + res.end() + }) + + https_server.listen(https_server.port, function() { + https_server.on('/https', function (req, res) { + res.writeHead(200) + res.end('https') + }) + https_server.on('/to_plain', function (req, res) { + res.writeHead(302, {'location':'http://localhost:'+plain_server.port + '/plain'}) + res.end() + }) + + run_tests() + run_tests({}) + run_tests({'http:':faux_http}) + run_tests({'https:':faux_https}) + run_tests({'http:':faux_http, 'https:':faux_https}) + }) +}) + +function run_tests(httpModules) { + var to_https = {'httpModules':httpModules, 'uri':'http://localhost:'+plain_server.port+'/to_https'} + var to_plain = {'httpModules':httpModules, 'uri':'https://localhost:'+https_server.port+'/to_plain'} + + request(to_https, function (er, res, body) { + assert.ok(!er, 'Bounce to SSL worked') + assert.equal(body, 'https', 'Received HTTPS server body') + done() + }) + + request(to_plain, function (er, res, body) { + assert.ok(!er, 'Bounce to plaintext server worked') + assert.equal(body, 'plain', 'Received HTTPS server body') + done() + }) +} + + +var passed = 0; +function done() { + passed += 1 + var expected = 10 + + if(passed == expected) { + plain_server.close() + https_server.close() + + assert.equal(faux_requests_made.http, 4, 'Wrapped http module called appropriately') + assert.equal(faux_requests_made.https, 4, 'Wrapped https module called appropriately') + + console.log((expected+2) + ' tests passed.') + } +} diff --git a/node_modules/request/tests/test-https.js b/node_modules/request/tests/test-https.js new file mode 100644 index 0000000..df7330b --- /dev/null +++ b/node_modules/request/tests/test-https.js @@ -0,0 +1,86 @@ +var server = require('./server') + , assert = require('assert') + , request = require('../main.js') + +var s = server.createSSLServer(); + +var tests = + { testGet : + { resp : server.createGetResponse("TESTING!") + , expectBody: "TESTING!" + } + , testGetChunkBreak : + { resp : server.createChunkResponse( + [ new Buffer([239]) + , new Buffer([163]) + , new Buffer([191]) + , new Buffer([206]) + , new Buffer([169]) + , new Buffer([226]) + , new Buffer([152]) + , new Buffer([131]) + ]) + , expectBody: "Ω☃" + } + , testGetJSON : + { resp : server.createGetResponse('{"test":true}', 'application/json') + , json : true + , expectBody: {"test":true} + } + , testPutString : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : "PUTTINGDATA" + } + , testPutBuffer : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : new Buffer("PUTTINGDATA") + } + , testPutJSON : + { resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) + , method: "PUT" + , json: {foo: 'bar'} + } + , testPutMultipart : + { resp: server.createPostValidator( + '--frontier\r\n' + + 'content-type: text/html\r\n' + + '\r\n' + + 'Oh hi.' + + '\r\n--frontier\r\n\r\n' + + 'Oh hi.' + + '\r\n--frontier--' + ) + , method: "PUT" + , multipart: + [ {'content-type': 'text/html', 'body': 'Oh hi.'} + , {'body': 'Oh hi.'} + ] + } + } + +s.listen(s.port, function () { + + var counter = 0 + + for (i in tests) { + (function () { + var test = tests[i] + s.on('/'+i, test.resp) + test.uri = s.url + '/' + i + request(test, function (err, resp, body) { + if (err) throw err + if (test.expectBody) { + assert.deepEqual(test.expectBody, body) + } + counter = counter - 1; + if (counter === 0) { + console.log(Object.keys(tests).length+" tests passed.") + s.close() + } + }) + counter++ + })() + } +}) diff --git a/node_modules/request/tests/test-oauth.js b/node_modules/request/tests/test-oauth.js new file mode 100644 index 0000000..e9d3290 --- /dev/null +++ b/node_modules/request/tests/test-oauth.js @@ -0,0 +1,117 @@ +var hmacsign = require('../oauth').hmacsign + , assert = require('assert') + , qs = require('querystring') + , request = require('../main') + ; + +function getsignature (r) { + var sign + r.headers.authorization.slice('OAuth '.length).replace(/,\ /g, ',').split(',').forEach(function (v) { + if (v.slice(0, 'oauth_signature="'.length) === 'oauth_signature="') sign = v.slice('oauth_signature="'.length, -1) + }) + return decodeURIComponent(sign) +} + +// Tests from Twitter documentation https://dev.twitter.com/docs/auth/oauth + +var reqsign = hmacsign('POST', 'https://api.twitter.com/oauth/request_token', + { oauth_callback: 'http://localhost:3005/the_dance/process_callback?service_provider_id=11' + , oauth_consumer_key: 'GDdmIQH6jhtmLUypg82g' + , oauth_nonce: 'QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk' + , oauth_signature_method: 'HMAC-SHA1' + , oauth_timestamp: '1272323042' + , oauth_version: '1.0' + }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98") + +console.log(reqsign) +console.log('8wUi7m5HFQy76nowoCThusfgB+Q=') +assert.equal(reqsign, '8wUi7m5HFQy76nowoCThusfgB+Q=') + +var accsign = hmacsign('POST', 'https://api.twitter.com/oauth/access_token', + { oauth_consumer_key: 'GDdmIQH6jhtmLUypg82g' + , oauth_nonce: '9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8' + , oauth_signature_method: 'HMAC-SHA1' + , oauth_token: '8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc' + , oauth_timestamp: '1272323047' + , oauth_verifier: 'pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY' + , oauth_version: '1.0' + }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98", "x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA") + +console.log(accsign) +console.log('PUw/dHA4fnlJYM6RhXk5IU/0fCc=') +assert.equal(accsign, 'PUw/dHA4fnlJYM6RhXk5IU/0fCc=') + +var upsign = hmacsign('POST', 'http://api.twitter.com/1/statuses/update.json', + { oauth_consumer_key: "GDdmIQH6jhtmLUypg82g" + , oauth_nonce: "oElnnMTQIZvqvlfXM56aBLAf5noGD0AQR3Fmi7Q6Y" + , oauth_signature_method: "HMAC-SHA1" + , oauth_token: "819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw" + , oauth_timestamp: "1272325550" + , oauth_version: "1.0" + , status: 'setting up my twitter 私のさえずりを設定する' + }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98", "J6zix3FfA9LofH0awS24M3HcBYXO5nI1iYe8EfBA") + +console.log(upsign) +console.log('yOahq5m0YjDDjfjxHaXEsW9D+X0=') +assert.equal(upsign, 'yOahq5m0YjDDjfjxHaXEsW9D+X0=') + + +var rsign = request.post( + { url: 'https://api.twitter.com/oauth/request_token' + , oauth: + { callback: 'http://localhost:3005/the_dance/process_callback?service_provider_id=11' + , consumer_key: 'GDdmIQH6jhtmLUypg82g' + , nonce: 'QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk' + , timestamp: '1272323042' + , version: '1.0' + , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" + } + }) + +setTimeout(function () { + console.log(getsignature(rsign)) + assert.equal(reqsign, getsignature(rsign)) +}) + +var raccsign = request.post( + { url: 'https://api.twitter.com/oauth/access_token' + , oauth: + { consumer_key: 'GDdmIQH6jhtmLUypg82g' + , nonce: '9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8' + , signature_method: 'HMAC-SHA1' + , token: '8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc' + , timestamp: '1272323047' + , verifier: 'pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY' + , version: '1.0' + , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" + , token_secret: "x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA" + } + }) + +setTimeout(function () { + console.log(getsignature(raccsign)) + assert.equal(accsign, getsignature(raccsign)) +}, 1) + +var rupsign = request.post( + { url: 'http://api.twitter.com/1/statuses/update.json' + , oauth: + { consumer_key: "GDdmIQH6jhtmLUypg82g" + , nonce: "oElnnMTQIZvqvlfXM56aBLAf5noGD0AQR3Fmi7Q6Y" + , signature_method: "HMAC-SHA1" + , token: "819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw" + , timestamp: "1272325550" + , version: "1.0" + , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" + , token_secret: "J6zix3FfA9LofH0awS24M3HcBYXO5nI1iYe8EfBA" + } + , form: {status: 'setting up my twitter 私のさえずりを設定する'} + }) +setTimeout(function () { + console.log(getsignature(rupsign)) + assert.equal(upsign, getsignature(rupsign)) +}, 1) + + + + diff --git a/node_modules/request/tests/test-pipes.js b/node_modules/request/tests/test-pipes.js new file mode 100644 index 0000000..5785475 --- /dev/null +++ b/node_modules/request/tests/test-pipes.js @@ -0,0 +1,182 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , fs = require('fs') + , request = require('../main.js') + , path = require('path') + , util = require('util') + ; + +var s = server.createServer(3453); + +function ValidationStream(str) { + this.str = str + this.buf = '' + this.on('data', function (data) { + this.buf += data + }) + this.on('end', function () { + assert.equal(this.str, this.buf) + }) + this.writable = true +} +util.inherits(ValidationStream, stream.Stream) +ValidationStream.prototype.write = function (chunk) { + this.emit('data', chunk) +} +ValidationStream.prototype.end = function (chunk) { + if (chunk) emit('data', chunk) + this.emit('end') +} + +s.listen(s.port, function () { + counter = 0; + + var check = function () { + counter = counter - 1 + if (counter === 0) { + console.log('All tests passed.') + setTimeout(function () { + process.exit(); + }, 500) + } + } + + // Test pipeing to a request object + s.once('/push', server.createPostValidator("mydata")); + + var mydata = new stream.Stream(); + mydata.readable = true + + counter++ + var r1 = request.put({url:'http://localhost:3453/push'}, function () { + check(); + }) + mydata.pipe(r1) + + mydata.emit('data', 'mydata'); + mydata.emit('end'); + + + // Test pipeing from a request object. + s.once('/pull', server.createGetResponse("mypulldata")); + + var mypulldata = new stream.Stream(); + mypulldata.writable = true + + counter++ + request({url:'http://localhost:3453/pull'}).pipe(mypulldata) + + var d = ''; + + mypulldata.write = function (chunk) { + d += chunk; + } + mypulldata.end = function () { + assert.equal(d, 'mypulldata'); + check(); + }; + + + s.on('/cat', function (req, resp) { + if (req.method === "GET") { + resp.writeHead(200, {'content-type':'text/plain-test', 'content-length':4}); + resp.end('asdf') + } else if (req.method === "PUT") { + assert.equal(req.headers['content-type'], 'text/plain-test'); + assert.equal(req.headers['content-length'], 4) + var validate = ''; + + req.on('data', function (chunk) {validate += chunk}) + req.on('end', function () { + resp.writeHead(201); + resp.end(); + assert.equal(validate, 'asdf'); + check(); + }) + } + }) + s.on('/pushjs', function (req, resp) { + if (req.method === "PUT") { + assert.equal(req.headers['content-type'], 'text/javascript'); + check(); + } + }) + s.on('/catresp', function (req, resp) { + request.get('http://localhost:3453/cat').pipe(resp) + }) + s.on('/doodle', function (req, resp) { + if (req.headers['x-oneline-proxy']) { + resp.setHeader('x-oneline-proxy', 'yup') + } + resp.writeHead('200', {'content-type':'image/png'}) + fs.createReadStream(path.join(__dirname, 'googledoodle.png')).pipe(resp) + }) + s.on('/onelineproxy', function (req, resp) { + var x = request('http://localhost:3453/doodle') + req.pipe(x) + x.pipe(resp) + }) + + counter++ + fs.createReadStream(__filename).pipe(request.put('http://localhost:3453/pushjs')) + + counter++ + request.get('http://localhost:3453/cat').pipe(request.put('http://localhost:3453/cat')) + + counter++ + request.get('http://localhost:3453/catresp', function (e, resp, body) { + assert.equal(resp.headers['content-type'], 'text/plain-test'); + assert.equal(resp.headers['content-length'], 4) + check(); + }) + + var doodleWrite = fs.createWriteStream(path.join(__dirname, 'test.png')) + + counter++ + request.get('http://localhost:3453/doodle').pipe(doodleWrite) + + doodleWrite.on('close', function () { + assert.deepEqual(fs.readFileSync(path.join(__dirname, 'googledoodle.png')), fs.readFileSync(path.join(__dirname, 'test.png'))) + check() + }) + + process.on('exit', function () { + fs.unlinkSync(path.join(__dirname, 'test.png')) + }) + + counter++ + request.get({uri:'http://localhost:3453/onelineproxy', headers:{'x-oneline-proxy':'nope'}}, function (err, resp, body) { + assert.equal(resp.headers['x-oneline-proxy'], 'yup') + check() + }) + + s.on('/afterresponse', function (req, resp) { + resp.write('d') + resp.end() + }) + + counter++ + var afterresp = request.post('http://localhost:3453/afterresponse').on('response', function () { + var v = new ValidationStream('d') + afterresp.pipe(v) + v.on('end', check) + }) + + s.on('/forward1', function (req, resp) { + resp.writeHead(302, {location:'/forward2'}) + resp.end() + }) + s.on('/forward2', function (req, resp) { + resp.writeHead('200', {'content-type':'image/png'}) + resp.write('d') + resp.end() + }) + + counter++ + var validateForward = new ValidationStream('d') + validateForward.on('end', check) + request.get('http://localhost:3453/forward1').pipe(validateForward) + +}) diff --git a/node_modules/request/tests/test-proxy.js b/node_modules/request/tests/test-proxy.js new file mode 100644 index 0000000..647157c --- /dev/null +++ b/node_modules/request/tests/test-proxy.js @@ -0,0 +1,39 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , fs = require('fs') + , request = require('../main.js') + , path = require('path') + , util = require('util') + ; + +var port = 6768 + , called = false + , proxiedHost = 'google.com' + ; + +var s = server.createServer(port) +s.listen(port, function () { + s.on('http://google.com/', function (req, res) { + called = true + assert.equal(req.headers.host, proxiedHost) + res.writeHeader(200) + res.end() + }) + request ({ + url: 'http://'+proxiedHost, + proxy: 'http://localhost:'+port + /* + //should behave as if these arguments where passed: + url: 'http://localhost:'+port, + headers: {host: proxiedHost} + //*/ + }, function (err, res, body) { + s.close() + }) +}) + +process.on('exit', function () { + assert.ok(called, 'the request must be made to the proxy server') +}) diff --git a/node_modules/request/tests/test-redirect.js b/node_modules/request/tests/test-redirect.js new file mode 100644 index 0000000..dfa086d --- /dev/null +++ b/node_modules/request/tests/test-redirect.js @@ -0,0 +1,76 @@ +var server = require('./server') + , assert = require('assert') + , request = require('../main.js') + +var s = server.createServer() + +s.listen(s.port, function () { + var server = 'http://localhost:' + s.port; + var hits = {} + var passed = 0; + + bouncer(301, 'temp') + bouncer(302, 'perm') + bouncer(302, 'nope') + + function bouncer(code, label) { + var landing = label+'_landing'; + + s.on('/'+label, function (req, res) { + hits[label] = true; + res.writeHead(code, {'location':server + '/'+landing}) + res.end() + }) + + s.on('/'+landing, function (req, res) { + hits[landing] = true; + res.writeHead(200) + res.end(landing) + }) + } + + // Permanent bounce + request(server+'/perm', function (er, res, body) { + try { + assert.ok(hits.perm, 'Original request is to /perm') + assert.ok(hits.perm_landing, 'Forward to permanent landing URL') + assert.equal(body, 'perm_landing', 'Got permanent landing content') + passed += 1 + } finally { + done() + } + }) + + // Temporary bounce + request(server+'/temp', function (er, res, body) { + try { + assert.ok(hits.temp, 'Original request is to /temp') + assert.ok(hits.temp_landing, 'Forward to temporary landing URL') + assert.equal(body, 'temp_landing', 'Got temporary landing content') + passed += 1 + } finally { + done() + } + }) + + // Prevent bouncing. + request({uri:server+'/nope', followRedirect:false}, function (er, res, body) { + try { + assert.ok(hits.nope, 'Original request to /nope') + assert.ok(!hits.nope_landing, 'No chasing the redirect') + assert.equal(res.statusCode, 302, 'Response is the bounce itself') + passed += 1 + } finally { + done() + } + }) + + var reqs_done = 0; + function done() { + reqs_done += 1; + if(reqs_done == 3) { + console.log(passed + ' tests passed.') + s.close() + } + } +}) diff --git a/node_modules/request/tests/test-timeout.js b/node_modules/request/tests/test-timeout.js new file mode 100644 index 0000000..673f8ad --- /dev/null +++ b/node_modules/request/tests/test-timeout.js @@ -0,0 +1,87 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , request = require('../main.js') + ; + +var s = server.createServer(); +var expectedBody = "waited"; +var remainingTests = 5; + +s.listen(s.port, function () { + // Request that waits for 200ms + s.on('/timeout', function (req, resp) { + setTimeout(function(){ + resp.writeHead(200, {'content-type':'text/plain'}) + resp.write(expectedBody) + resp.end() + }, 200); + }); + + // Scenario that should timeout + var shouldTimeout = { + url: s.url + "/timeout", + timeout:100 + } + + + request(shouldTimeout, function (err, resp, body) { + assert.equal(err.code, "ETIMEDOUT"); + checkDone(); + }) + + + // Scenario that shouldn't timeout + var shouldntTimeout = { + url: s.url + "/timeout", + timeout:300 + } + + request(shouldntTimeout, function (err, resp, body) { + assert.equal(err, null); + assert.equal(expectedBody, body) + checkDone(); + }) + + // Scenario with no timeout set, so shouldn't timeout + var noTimeout = { + url: s.url + "/timeout" + } + + request(noTimeout, function (err, resp, body) { + assert.equal(err); + assert.equal(expectedBody, body) + checkDone(); + }) + + // Scenario with a negative timeout value, should be treated a zero or the minimum delay + var negativeTimeout = { + url: s.url + "/timeout", + timeout:-1000 + } + + request(negativeTimeout, function (err, resp, body) { + assert.equal(err.code, "ETIMEDOUT"); + checkDone(); + }) + + // Scenario with a float timeout value, should be rounded by setTimeout anyway + var floatTimeout = { + url: s.url + "/timeout", + timeout: 100.76 + } + + request(floatTimeout, function (err, resp, body) { + assert.equal(err.code, "ETIMEDOUT"); + checkDone(); + }) + + function checkDone() { + if(--remainingTests == 0) { + s.close(); + console.log("All tests passed."); + } + } +}) + diff --git a/node_modules/request/uuid.js b/node_modules/request/uuid.js new file mode 100644 index 0000000..1d83bd5 --- /dev/null +++ b/node_modules/request/uuid.js @@ -0,0 +1,19 @@ +module.exports = function () { + var s = [], itoh = '0123456789ABCDEF'; + + // Make array of random hex digits. The UUID only has 32 digits in it, but we + // allocate an extra items to make room for the '-'s we'll be inserting. + for (var i = 0; i <36; i++) s[i] = Math.floor(Math.random()*0x10); + + // Conform to RFC-4122, section 4.4 + s[14] = 4; // Set 4 high bits of time_high field to version + s[19] = (s[19] & 0x3) | 0x8; // Specify 2 high bits of clock sequence + + // Convert to hex chars + for (var i = 0; i <36; i++) s[i] = itoh[s[i]]; + + // Insert '-'s + s[8] = s[13] = s[18] = s[23] = '-'; + + return s.join(''); +} diff --git a/node_modules/request/vendor/cookie/index.js b/node_modules/request/vendor/cookie/index.js new file mode 100644 index 0000000..1eb2eaa --- /dev/null +++ b/node_modules/request/vendor/cookie/index.js @@ -0,0 +1,60 @@ +/*! + * Tobi - Cookie + * Copyright(c) 2010 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var url = require('url'); + +/** + * Initialize a new `Cookie` with the given cookie `str` and `req`. + * + * @param {String} str + * @param {IncomingRequest} req + * @api private + */ + +var Cookie = exports = module.exports = function Cookie(str, req) { + this.str = str; + + // First key is the name + this.name = str.substr(0, str.indexOf('=')).trim(); + + // Map the key/val pairs + str.split(/ *; */).reduce(function(obj, pair){ + var p = pair.indexOf('='); + if(p > 0) + obj[pair.substring(0, p).trim()] = pair.substring(p + 1).trim(); + else + obj[pair.trim()] = true; + return obj; + }, this); + + // Assign value + this.value = this[this.name]; + + // Expires + this.expires = this.expires + ? new Date(this.expires) + : Infinity; + + // Default or trim path + this.path = this.path + ? this.path.trim(): req + ? url.parse(req.url).pathname: '/'; +}; + +/** + * Return the original cookie string. + * + * @return {String} + * @api public + */ + +Cookie.prototype.toString = function(){ + return this.str; +}; diff --git a/node_modules/request/vendor/cookie/jar.js b/node_modules/request/vendor/cookie/jar.js new file mode 100644 index 0000000..34920e0 --- /dev/null +++ b/node_modules/request/vendor/cookie/jar.js @@ -0,0 +1,72 @@ +/*! +* Tobi - CookieJar +* Copyright(c) 2010 LearnBoost +* MIT Licensed +*/ + +/** +* Module dependencies. +*/ + +var url = require('url'); + +/** +* Initialize a new `CookieJar`. +* +* @api private +*/ + +var CookieJar = exports = module.exports = function CookieJar() { + this.cookies = []; +}; + +/** +* Add the given `cookie` to the jar. +* +* @param {Cookie} cookie +* @api private +*/ + +CookieJar.prototype.add = function(cookie){ + this.cookies = this.cookies.filter(function(c){ + // Avoid duplication (same path, same name) + return !(c.name == cookie.name && c.path == cookie.path); + }); + this.cookies.push(cookie); +}; + +/** +* Get cookies for the given `req`. +* +* @param {IncomingRequest} req +* @return {Array} +* @api private +*/ + +CookieJar.prototype.get = function(req){ + var path = url.parse(req.url).pathname + , now = new Date + , specificity = {}; + return this.cookies.filter(function(cookie){ + if (0 == path.indexOf(cookie.path) && now < cookie.expires + && cookie.path.length > (specificity[cookie.name] || 0)) + return specificity[cookie.name] = cookie.path.length; + }); +}; + +/** +* Return Cookie string for the given `req`. +* +* @param {IncomingRequest} req +* @return {String} +* @api private +*/ + +CookieJar.prototype.cookieString = function(req){ + var cookies = this.get(req); + if (cookies.length) { + return cookies.map(function(cookie){ + return cookie.name + '=' + cookie.value; + }).join('; '); + } +}; diff --git a/node_modules/should/.gitmodules b/node_modules/should/.gitmodules new file mode 100644 index 0000000..ffe0dcb --- /dev/null +++ b/node_modules/should/.gitmodules @@ -0,0 +1,3 @@ +[submodule "support/expresso"] + path = support/expresso + url = git://github.com/visionmedia/expresso.git diff --git a/node_modules/should/.npmignore b/node_modules/should/.npmignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/node_modules/should/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/node_modules/should/History.md b/node_modules/should/History.md new file mode 100644 index 0000000..eec7ed3 --- /dev/null +++ b/node_modules/should/History.md @@ -0,0 +1,100 @@ + +0.6.0 / 2012-03-01 +================== + + * Added `err.actual` and `err.expected` for .{eql,equal}() + * Added 'return this;' to 'get json' and 'get html' in order to provide chaining for should.be.json and should.be.html + +0.5.1 / 2012-01-13 +================== + + * Added better `.json` + * Added better `.html` + +0.5.0 / 2012-01-12 +================== + + * Added string matching to `.throw()` [serby] + * Added regexp matching to `.throw()` [serby] + * Added `.includeEql()` [RubenVerborgh] + * Added `.should.be.html` + * Added `.should.be.json` + * Added optional description args to most matchers [Mike Swift] + +0.4.2 / 2011-12-17 +================== + + * Fixed .header() for realzzz + +0.4.1 / 2011-12-16 +================== + + * Fixed: chain .header() to retain negation + +0.4.0 / 2011-12-16 +================== + + * Added `.should.throw()` + * Added `.include()` support for strings + * Added `.include()` support for arrays + * Removed `keys()` `.include` modifier support + * Removed `.object()` + * Removed `.string()` + * Removed `.contain()` + * Removed `.respondTo()` rubyism + * expresso -> mocha + +0.3.2 / 2011-10-24 +================== + + * Fixed tests for 0.5.x + * Fixed sys warning + +0.3.1 / 2011-08-22 +================== + + * configurable + +0.3.0 / 2011-08-20 +================== + + * Added assertion for inclusion of an object: `foo.should.include.object({ foo: 'bar' })` + +0.2.1 / 2011-05-13 +================== + + * Fixed .status(code). Closes #18 + +0.2.0 / 2011-04-17 +================== + + * Added `res.should.have.status(code)` method + * Added `res.should.have.header(field, val)` method + +0.1.0 / 2011-04-06 +================== + + * Added `should.exist(obj)` [aseemk] + * Added `should.not.exist(obj)` [aseemk] + +0.0.4 / 2010-11-24 +================== + + * Added `.ok` to assert truthfulness + * Added `.arguments` + * Fixed double required bug. [thanks dominictarr] + +0.0.3 / 2010-11-19 +================== + + * Added `true` / `false` assertions + +0.0.2 / 2010-11-19 +================== + + * Added chaining support + +0.0.1 / 2010-11-19 +================== + + * Initial release diff --git a/node_modules/should/Makefile b/node_modules/should/Makefile new file mode 100644 index 0000000..19e8868 --- /dev/null +++ b/node_modules/should/Makefile @@ -0,0 +1,6 @@ + +test: + @./node_modules/.bin/mocha \ + --ui exports + +.PHONY: test \ No newline at end of file diff --git a/node_modules/should/Readme.md b/node_modules/should/Readme.md new file mode 100644 index 0000000..2036df1 --- /dev/null +++ b/node_modules/should/Readme.md @@ -0,0 +1,367 @@ +_should_ is an expressive, readable, test framework agnostic, assertion library for [node](http://nodejs.org). + +It extends the Object prototype with a single non-enumerable getter that allows you to express how that object should behave. + +_should_ literally extends node's _assert_ module, in fact, it is node's assert module, for example `should.equal(str, 'foo')` will work, just as `assert.equal(str, 'foo')` would, and `should.AssertionError` **is** `assert.AssertionError`, meaning any test framework supporting this constructor will function properly with _should_. + +## Example + + var user = { + name: 'tj' + , pets: ['tobi', 'loki', 'jane', 'bandit'] + }; + + user.should.have.property('name', 'tj'); + user.should.have.property('pets').with.lengthOf(4); + + someAsyncTask(foo, function(err, result){ + should.not.exist(err); + should.exist(result); + result.bar.should.equal(foo); + }); + +## Installation + + $ npm install should + +## assert extras + +As mentioned above, _should_ extends node's _assert_. The returned object from `require('should')` is thus similar to the returned object from `require('assert')`, but it has one extra convenience method: + + should.exist('hello') + should.exist([]) + should.exist(null) // will throw + +This is equivalent to `should.ok`, which is equivalent to `assert.ok`, but reads a bit better. It gets better, though: + + should.not.exist(false) + should.not.exist('') + should.not.exist({}) // will throw + +We may add more _assert_ extras in the future... ;) + +## chaining assertions + +Some assertions can be chained, for example if a property is volatile we can first assert property existence: + + user.should.have.property('pets').with.lengthOf(4) + +which is essentially equivalent to below, however the property may not exist: + + user.pets.should.have.lengthOf(4) + +our dummy getters such as _and_ also help express chaining: + + user.should.be.a('object').and.have.property('name', 'tj') + +## exist (static) + +The returned object from `require('should')` is the same object as `require('assert')`. So you can use `should` just like `assert`: + + should.fail('expected an error!') + should.strictEqual(foo, bar) + +In general, using the Object prototype's _should_ is nicer than using these `assert` equivalents, because _should_ gives you access to the expressive and readable language described above: + + foo.should.equal(bar) // same as should.strictEqual(foo, bar) above + +The only exception, though, is when you can't be sure that a particular object exists. In that case, attempting to access the _should_ property may throw a TypeError: + + foo.should.equal(bar) // throws if foo is null or undefined! + +For this case, `require('should')` extends `require('assert')` with an extra convenience method to check whether an object exists: + + should.exist({}) + should.exist([]) + should.exist('') + should.exist(0) + should.exist(null) // will throw + should.exist(undefined) // will throw + +You can also check the negation: + + should.not.exist(undefined) + should.not.exist(null) + should.not.exist('') // will throw + should.not.exist({}) // will throw + +Once you know an object exists, you can safely use the _should_ property on it. + +## ok + +Assert truthfulness: + + true.should.be.ok + 'yay'.should.be.ok + (1).should.be.ok + +or negated: + + false.should.not.be.ok + ''.should.not.be.ok + (0).should.not.be.ok + +## true + +Assert === true: + + true.should.be.true + '1'.should.not.be.true + +## false + +Assert === false: + + false.should.be.false + (0).should.not.be.false + +## arguments + +Assert `Arguments`: + + var args = (function(){ return arguments; })(1,2,3); + args.should.be.arguments; + [].should.not.be.arguments; + +## empty + +Asserts that length is 0: + + [].should.be.empty + ''.should.be.empty + ({ length: 0 }).should.be.empty + +## eql + +equality: + + ({ foo: 'bar' }).should.eql({ foo: 'bar' }) + [1,2,3].should.eql([1,2,3]) + +## equal + +strict equality: + + should.strictEqual(undefined, value) + should.strictEqual(false, value) + (4).should.equal(4) + 'test'.should.equal('test') + [1,2,3].should.not.equal([1,2,3]) + +## within + +Assert inclusive numeric range: + + user.age.should.be.within(5, 50) + +## a + +Assert __typeof__: + + user.should.be.a('object') + 'test'.should.be.a('string') + +## instanceof + +Assert __instanceof__: + + user.should.be.an.instanceof(User) + [].should.be.an.instanceof(Array) + +## above + +Assert numeric value above the given value: + + user.age.should.be.above(5) + user.age.should.not.be.above(100) + +## below + +Assert numeric value below the given value: + + user.age.should.be.below(100) + user.age.should.not.be.below(5) + +## match + +Assert regexp match: + + username.should.match(/^\w+$/) + +## length + +Assert _length_ property exists and has a value of the given number: + + user.pets.should.have.length(5) + user.pets.should.have.a.lengthOf(5) + +Aliases: _lengthOf_ + +## property + +Assert property exists and has optional value: + + user.should.have.property('name') + user.should.have.property('age', 15) + user.should.not.have.property('rawr') + user.should.not.have.property('age', 0) + +## ownProperty + +Assert own property (on the immediate object): + + ({ foo: 'bar' }).should.have.ownProperty('foo') + +## status(code) + + Asserts that `.statusCode` is `code`: + + res.should.have.status(200); + +## header(field[, value]) + + Asserts that a `.headers` object with `field` and optional `value` are present: + + res.should.have.header('content-length'); + res.should.have.header('Content-Length', '123'); + res.should.have.header('content-length', '123'); + +## json + + Assert that Content-Type is "application/json; charset=utf-8" + + res.should.be.json + +## html + + Assert that Content-Type is "text/html; charset=utf-8" + + res.should.be.html + +## include(obj) + +Assert that the given `obj` is present via `indexOf()`, so this works for strings, arrays, or custom objects implementing indexOf: + +Assert array value: + + [1,2,3].should.include(3) + [1,2,3].should.include(2) + [1,2,3].should.not.include(4) + +Assert substring: + + 'foo bar baz'.should.include('foo') + 'foo bar baz'.should.include('bar') + 'foo bar baz'.should.include('baz') + 'foo bar baz'.should.not.include('FOO') + +## includeEql(obj) + +Assert that an object equal to the given `obj` is present in an Array: + + [[1],[2],[3]].should.includeEql([3]) + [[1],[2],[3]].should.includeEql([2]) + [[1],[2],[3]].should.not.includeEql([4]) + +## throw() + +Assert an exception is thrown: + +```js +(function(){ + throw new Error('fail'); +}).should.throw(); +``` + +Assert an exception is not thrown: + +```js +(function(){ + +}).should.not.throw(); +``` +Assert exepection message matches string: + +```js +(function(){ + throw new Error('fail'); +}).should.throw('fail'); +``` + +Assert exepection message matches regexp: + +```js +(function(){ + throw new Error('failed to foo'); +}).should.throw(/^fail/); +``` + +## keys + +Assert own object keys, which must match _exactly_, +and will fail if you omit a key or two: + + var obj = { foo: 'bar', baz: 'raz' }; + obj.should.have.keys('foo', 'bar'); + obj.should.have.keys(['foo', 'bar']); + +## Optional Error description + +As it can often be difficult to assertain exactly where failed assertions are comming from in your tests, an optional description parameter can be passed to several should matchers. The description will follow the failed assertion in the error: + + (1).should.eql(0, 'some useful description') + + AssertionError: expected 1 to equal 0 | some useful description + at Object.eql (/Users/swift/code/should.js/node_modules/should/lib/should.js:280:10) + ... + +The methods that support this optional description are: `eql`, `equal`, `within`, `a`, `instanceof`, `above`, `below`, `match`, `length`, `property`, `ownProperty`, `include`, and `includeEql`. + +## Express example + +For example you can use should with the [Expresso TDD Framework](http://github.com/visionmedia/expresso) by simply including it: + + var lib = require('mylib') + , should = require('should'); + + module.exports = { + 'test .version': function(){ + lib.version.should.match(/^\d+\.\d+\.\d+$/); + } + }; + +## Running tests + +To run the tests for _should_ simple update your git submodules and run: + + $ make test + +## OMG IT EXTENDS OBJECT???!?!@ + +Yes, yes it does, with a single getter _should_, and no it wont break your code, because it does this **properly** with a non-enumerable property. + +## License + +(The MIT License) + +Copyright (c) 2010-2011 TJ Holowaychuk <tj@vision-media.ca> +Copyright (c) 2011 Aseem Kishore <aseem.kishore@gmail.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/should/examples/runner.js b/node_modules/should/examples/runner.js new file mode 100644 index 0000000..9355955 --- /dev/null +++ b/node_modules/should/examples/runner.js @@ -0,0 +1,53 @@ + +/** + * Module dependencies. + */ + +var should = require('../'); + +function test(name, fn){ + try { + fn(); + } catch (err) { + console.log(' \x1b[31m%s', name); + console.log(' %s\x1b[0m', err.stack); + return; + } + console.log(' √ \x1b[32m%s\x1b[0m', name); +} + +function Point(x, y) { + this.x = x; + this.y = y; + this.sub = function(other){ + return new Point( + this.x - other.x + , this.y - other.y); + } +} + +console.log(); + +test('new Point(x, y)', function(){ + var point = new Point(50, 100); + point.should.be.an.instanceof(Point); + point.should.have.property('x', 50); + point.should.have.property('y', 100); +}); + +test('Point#sub()', function(){ + var a = new Point(50, 100) + , b = new Point(20, 50); + a.sub(b).should.be.an.instanceof(Point); + a.sub(b).should.not.equal(a); + a.sub(b).should.not.equal(b); + a.sub(b).should.have.property('x', 30); + a.sub(b).should.have.property('y', 50); +}); + +test('Point#add()', function(){ + var point = new Point(50, 100); + point.should.respondTo('add'); +}); + +console.log(); \ No newline at end of file diff --git a/node_modules/should/index.js b/node_modules/should/index.js new file mode 100644 index 0000000..6dbdbde --- /dev/null +++ b/node_modules/should/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/should'); \ No newline at end of file diff --git a/node_modules/should/lib/eql.js b/node_modules/should/lib/eql.js new file mode 100644 index 0000000..ef21923 --- /dev/null +++ b/node_modules/should/lib/eql.js @@ -0,0 +1,91 @@ + +// Taken from node's assert module, because it sucks +// and exposes next to nothing useful. + +module.exports = _deepEqual; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3. Other pairs that do not both pass typeof value == "object", + // equivalence is determined by ==. + } else if (typeof actual != 'object' && typeof expected != 'object') { + return actual === expected; + + // 7.4. For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical "prototype" property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isUndefinedOrNull (value) { + return value === null || value === undefined; +} + +function isArguments (object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv (a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // an identical "prototype" property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + 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; +} diff --git a/node_modules/should/lib/should.js b/node_modules/should/lib/should.js new file mode 100644 index 0000000..0273287 --- /dev/null +++ b/node_modules/should/lib/should.js @@ -0,0 +1,701 @@ +/*! + * Should + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var util = require('util') + , http = require('http') + , assert = require('assert') + , AssertionError = assert.AssertionError + , statusCodes = http.STATUS_CODES + , eql = require('./eql') + , i = util.inspect; + +/** + * Expose assert as should. + * + * This allows you to do things like below + * without require()ing the assert module. + * + * should.equal(foo.bar, undefined); + * + */ + +exports = module.exports = assert; + +/** + * Library version. + */ + +exports.version = '0.6.0'; + +/** + * Assert _obj_ exists, with optional message. + * + * @param {Mixed} obj + * @param {String} msg + * @api public + */ + +exports.exist = function(obj, msg){ + if (null == obj) { + throw new AssertionError({ + message: msg || ('expected ' + i(obj) + ' to exist') + , stackStartFunction: should.exist + }); + } +}; + +/** + * Asserts _obj_ does not exist, with optional message. + * + * @param {Mixed} obj + * @param {String} msg + * @api public + */ + +exports.not = {}; +exports.not.exist = function(obj, msg){ + if (null != obj) { + throw new AssertionError({ + message: msg || ('expected ' + i(obj) + ' to not exist') + , stackStartFunction: should.not.exist + }); + } +}; + +/** + * Expose api via `Object#should`. + * + * @api public + */ + +Object.defineProperty(Object.prototype, 'should', { + set: function(){}, + get: function(){ + return new Assertion(this); + }, + configurable: true +}); + +/** + * Initialize a new `Assertion` with the given _obj_. + * + * @param {Mixed} obj + * @api private + */ + +var Assertion = exports.Assertion = function Assertion(obj) { + this.obj = obj; +}; + +/** + * Prototype. + */ + +Assertion.prototype = { + + /** + * HACK: prevents double require() from failing. + */ + + exports: exports, + + /** + * Assert _expr_ with the given _msg_ and _negatedMsg_. + * + * @param {Boolean} expr + * @param {String} msg + * @param {String} negatedMsg + * @param {Object} expected + * @api private + */ + + assert: function(expr, msg, negatedMsg, expected){ + var msg = this.negate ? negatedMsg : msg + , ok = this.negate ? !expr : expr; + + if (!ok) { + throw new AssertionError({ + message: msg + , actual: this.obj + , expected: expected + , stackStartFunction: this.assert + }); + } + }, + + /** + * Dummy getter. + * + * @api public + */ + + get an() { + return this; + }, + + /** + * Dummy getter. + * + * @api public + */ + + get and() { + return this; + }, + + /** + * Dummy getter. + * + * @api public + */ + + get be() { + return this; + }, + + /** + * Dummy getter. + * + * @api public + */ + + get have() { + return this; + }, + + /** + * Dummy getter. + * + * @api public + */ + + get with() { + return this; + }, + + /** + * Negation modifier. + * + * @api public + */ + + get not() { + this.negate = true; + return this; + }, + + /** + * Get object inspection string. + * + * @return {String} + * @api private + */ + + get inspect() { + return i(this.obj); + }, + + /** + * Assert instanceof `Arguments`. + * + * @api public + */ + + get arguments() { + this.assert( + '[object Arguments]' == Object.prototype.toString.call(this.obj) + , 'expected ' + this.inspect + ' to be arguments' + , 'expected ' + this.inspect + ' to not be arguments'); + return this; + }, + + /** + * Assert that an object is empty aka length of 0. + * + * @api public + */ + + get empty() { + this.obj.should.have.property('length'); + this.assert( + 0 === this.obj.length + , 'expected ' + this.inspect + ' to be empty' + , 'expected ' + this.inspect + ' not to be empty'); + return this; + }, + + /** + * Assert ok. + * + * @api public + */ + + get ok() { + this.assert( + this.obj + , 'expected ' + this.inspect + ' to be truthy' + , 'expected ' + this.inspect + ' to be falsey'); + return this; + }, + + /** + * Assert true. + * + * @api public + */ + + get true() { + this.assert( + true === this.obj + , 'expected ' + this.inspect + ' to be true' + , 'expected ' + this.inspect + ' not to be true'); + return this; + }, + + /** + * Assert false. + * + * @api public + */ + + get false() { + this.assert( + false === this.obj + , 'expected ' + this.inspect + ' to be false' + , 'expected ' + this.inspect + ' not to be false'); + return this; + }, + + /** + * Assert equal. + * + * @param {Mixed} val + * @param {String} description + * @api public + */ + + eql: function(val, desc){ + this.assert( + eql(val, this.obj) + , 'expected ' + this.inspect + ' to equal ' + i(val) + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not equal ' + i(val) + (desc ? " | " + desc : "") + , val); + return this; + }, + + /** + * Assert strict equal. + * + * @param {Mixed} val + * @param {String} description + * @api public + */ + + equal: function(val, desc){ + this.assert( + val === this.obj + , 'expected ' + this.inspect + ' to equal ' + i(val) + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not equal ' + i(val) + (desc ? " | " + desc : "") + , val); + return this; + }, + + /** + * Assert within start to finish (inclusive). + * + * @param {Number} start + * @param {Number} finish + * @param {String} description + * @api public + */ + + within: function(start, finish, desc){ + var range = start + '..' + finish; + this.assert( + this.obj >= start && this.obj <= finish + , 'expected ' + this.inspect + ' to be within ' + range + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not be within ' + range + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert typeof. + * + * @param {Mixed} type + * @param {String} description + * @api public + */ + + a: function(type, desc){ + this.assert( + type == typeof this.obj + , 'expected ' + this.inspect + ' to be a ' + type + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' not to be a ' + type + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert instanceof. + * + * @param {Function} constructor + * @param {String} description + * @api public + */ + + instanceof: function(constructor, desc){ + var name = constructor.name; + this.assert( + this.obj instanceof constructor + , 'expected ' + this.inspect + ' to be an instance of ' + name + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' not to be an instance of ' + name + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert numeric value above _n_. + * + * @param {Number} n + * @param {String} description + * @api public + */ + + above: function(n, desc){ + this.assert( + this.obj > n + , 'expected ' + this.inspect + ' to be above ' + n + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to be below ' + n + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert numeric value below _n_. + * + * @param {Number} n + * @param {String} description + * @api public + */ + + below: function(n, desc){ + this.assert( + this.obj < n + , 'expected ' + this.inspect + ' to be below ' + n + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to be above ' + n + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert string value matches _regexp_. + * + * @param {RegExp} regexp + * @param {String} description + * @api public + */ + + match: function(regexp, desc){ + this.assert( + regexp.exec(this.obj) + , 'expected ' + this.inspect + ' to match ' + regexp + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' not to match ' + regexp + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert property "length" exists and has value of _n_. + * + * @param {Number} n + * @param {String} description + * @api public + */ + + length: function(n, desc){ + this.obj.should.have.property('length'); + var len = this.obj.length; + this.assert( + n == len + , 'expected ' + this.inspect + ' to have a length of ' + n + ' but got ' + len + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not have a length of ' + len + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert property _name_ exists, with optional _val_. + * + * @param {String} name + * @param {Mixed} val + * @param {String} description + * @api public + */ + + property: function(name, val, desc){ + if (this.negate && undefined !== val) { + if (undefined === this.obj[name]) { + throw new Error(this.inspect + ' has no property ' + i(name) + (desc ? " | " + desc : "")); + } + } else { + this.assert( + undefined !== this.obj[name] + , 'expected ' + this.inspect + ' to have a property ' + i(name) + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not have a property ' + i(name) + (desc ? " | " + desc : "")); + } + + if (undefined !== val) { + this.assert( + val === this.obj[name] + , 'expected ' + this.inspect + ' to have a property ' + i(name) + + ' of ' + i(val) + ', but got ' + i(this.obj[name]) + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not have a property ' + i(name) + ' of ' + i(val) + (desc ? " | " + desc : "")); + } + + this.obj = this.obj[name]; + return this; + }, + + /** + * Assert own property _name_ exists. + * + * @param {String} name + * @param {String} description + * @api public + */ + + ownProperty: function(name, desc){ + this.assert( + this.obj.hasOwnProperty(name) + , 'expected ' + this.inspect + ' to have own property ' + i(name) + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not have own property ' + i(name) + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert that `obj` is present via `.indexOf()`. + * + * @param {Mixed} obj + * @param {String} description + * @api public + */ + + include: function(obj, desc){ + this.assert( + ~this.obj.indexOf(obj) + , 'expected ' + this.inspect + ' to include ' + i(obj) + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not include ' + i(obj) + (desc ? " | " + desc : "")); + + return this; + }, + + /** + * Assert that an object equal to `obj` is present. + * + * @param {Array} obj + * @param {String} description + * @api public + */ + + includeEql: function(obj, desc){ + this.assert( + this.obj.some(function(item) { return eql(obj, item); }) + , 'expected ' + this.inspect + ' to include an object equal to ' + i(obj) + (desc ? " | " + desc : "") + , 'expected ' + this.inspect + ' to not include an object equal to ' + i(obj) + (desc ? " | " + desc : "")); + return this; + }, + + /** + * Assert that the array contains _obj_. + * + * @param {Mixed} obj + * @api public + */ + + contain: function(obj){ + console.warn('should.contain() is deprecated, use should.include()'); + this.obj.should.be.an.instanceof(Array); + this.assert( + ~this.obj.indexOf(obj) + , 'expected ' + this.inspect + ' to contain ' + i(obj) + , 'expected ' + this.inspect + ' to not contain ' + i(obj)); + return this; + }, + + /** + * Assert exact keys or inclusion of keys by using + * the `.include` modifier. + * + * @param {Array|String ...} keys + * @api public + */ + + keys: function(keys){ + var str + , ok = true; + + keys = keys instanceof Array + ? keys + : Array.prototype.slice.call(arguments); + + if (!keys.length) throw new Error('keys required'); + + var actual = Object.keys(this.obj) + , len = keys.length; + + // make sure they're all present + ok = keys.every(function(key){ + return ~actual.indexOf(key); + }); + + // matching length + ok = ok && keys.length == actual.length; + + // key string + if (len > 1) { + keys = keys.map(function(key){ + return i(key); + }); + var last = keys.pop(); + str = keys.join(', ') + ', and ' + last; + } else { + str = i(keys[0]); + } + + // message + str = 'have ' + (len > 1 ? 'keys ' : 'key ') + str; + + this.assert( + ok + , 'expected ' + this.inspect + ' to ' + str + , 'expected ' + this.inspect + ' to not ' + str); + + return this; + }, + + /** + * Assert that header `field` has the given `val`. + * + * @param {String} field + * @param {String} val + * @return {Assertion} for chaining + * @api public + */ + + header: function(field, val){ + this.obj.should + .have.property('headers').and + .have.property(field.toLowerCase(), val); + return this; + }, + + /** + * Assert `.statusCode` of `code`. + * + * @param {Number} code + * @return {Assertion} for chaining + * @api public + */ + + status: function(code){ + this.obj.should.have.property('statusCode'); + var status = this.obj.statusCode; + + this.assert( + code == status + , 'expected response code of ' + code + ' ' + i(statusCodes[code]) + + ', but got ' + status + ' ' + i(statusCodes[status]) + , 'expected to not respond with ' + code + ' ' + i(statusCodes[code])); + + return this; + }, + + /** + * Assert that this response has content-type: application/json. + * + * @return {Assertion} for chaining + * @api public + */ + + get json() { + this.obj.should.have.property('headers'); + this.obj.headers.should.have.property('content-type'); + this.obj.headers['content-type'].should.include('application/json'); + return this; + }, + + /** + * Assert that this response has content-type: text/html. + * + * @return {Assertion} for chaining + * @api public + */ + + get html() { + this.obj.should.have.property('headers'); + this.obj.headers.should.have.property('content-type'); + this.obj.headers['content-type'].should.include('text/html'); + return this; + }, + + /** + * Assert that this function will or will not + * throw an exception. + * + * @return {Assertion} for chaining + * @api public + */ + + throw: function(message){ + var fn = this.obj + , err = {} + , errorInfo = '' + , ok = true; + + try { + fn(); + ok = false; + } catch (e) { + err = e; + } + + if (ok) { + if ('string' == typeof message) { + ok = message == err.message; + } else if (message instanceof RegExp) { + ok = message.test(err.message); + } + + if (message && !ok) { + if ('string' == typeof message) { + errorInfo = " with a message matching '" + message + "', but got '" + err.message + "'"; + } else { + errorInfo = " with a message matching " + message + ", but got '" + err.message + "'"; + } + } + } + + this.assert( + ok + , 'expected an exception to be thrown' + errorInfo + , 'expected no exception to be thrown, got "' + err.message + '"'); + + return this; + } +}; + +/** + * Aliases. + */ + +(function alias(name, as){ + Assertion.prototype[as] = Assertion.prototype[name]; + return alias; +}) +('length', 'lengthOf') +('keys', 'key') +('ownProperty', 'haveOwnProperty') +('above', 'greaterThan') +('below', 'lessThan'); + diff --git a/node_modules/should/package.json b/node_modules/should/package.json new file mode 100644 index 0000000..f13b3cc --- /dev/null +++ b/node_modules/should/package.json @@ -0,0 +1,39 @@ +{ + "name": "should", + "description": "test framework agnostic BDD-style assertions", + "version": "0.6.0", + "author": { + "name": "TJ Holowaychuk", + "email": "tj@vision-media.ca" + }, + "contributors": [ + { + "name": "Aseem Kishore", + "email": "aseem.kishore@gmail.com" + } + ], + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [ + "test", + "bdd", + "assert" + ], + "main": "./lib/should.js", + "engines": { + "node": ">= 0.2.0" + }, + "_id": "should@0.6.0", + "dependencies": {}, + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "74fa952c938f83e5c0daafbb96f1790e0a67fd62" + }, + "_from": "should@>= 0.6.0" +} diff --git a/node_modules/should/test/exist.test.js b/node_modules/should/test/exist.test.js new file mode 100644 index 0000000..7aa1082 --- /dev/null +++ b/node_modules/should/test/exist.test.js @@ -0,0 +1,96 @@ + +/** + * Module dependencies. + */ + +var should = require('../'); +var util = require('util'); + +function err(fn, msg) { + try { + fn(); + should.fail('expected an error'); + } catch (err) { + should.equal(msg, err.message); + } +} + +function err_should_exist(obj) { + err(function () { + should.exist(obj); + }, 'expected ' + util.inspect(obj) + ' to exist'); +} + +function err_should_not_exist(obj) { + err(function () { + should.not.exist(obj); + }, 'expected ' + util.inspect(obj) + ' to not exist'); +} + +module.exports = { + + // static should.exist() pass: + + 'test static should.exist() pass w/ bool': function () { + should.exist(false); + }, + + 'test static should.exist() pass w/ number': function () { + should.exist(0); + }, + + 'test static should.exist() pass w/ string': function () { + should.exist(''); + }, + + 'test static should.exist() pass w/ object': function () { + should.exist({}); + }, + + 'test static should.exist() pass w/ array': function () { + should.exist([]); + }, + + // static should.exist() fail: + + 'test static should.exist() fail w/ null': function () { + err_should_exist(null); + }, + + 'test static should.exist() fail w/ undefined': function () { + err_should_exist(undefined); + }, + + // static should.not.exist() pass: + + 'test static should.not.exist() pass w/ null': function () { + should.not.exist(null); + }, + + 'test static should.not.exist() pass w/ undefined': function () { + should.not.exist(undefined); + }, + + // static should.not.exist() fail: + + 'test static should.not.exist() fail w/ bool': function () { + err_should_not_exist(false); + }, + + 'test static should.not.exist() fail w/ number': function () { + err_should_not_exist(0); + }, + + 'test static should.not.exist() fail w/ string': function () { + err_should_not_exist(''); + }, + + 'test static should.not.exist() fail w/ object': function () { + err_should_not_exist({}); + }, + + 'test static should.not.exist() fail w/ array': function () { + err_should_not_exist([]); + }, + +}; diff --git a/node_modules/should/test/should.test.js b/node_modules/should/test/should.test.js new file mode 100644 index 0000000..a910f31 --- /dev/null +++ b/node_modules/should/test/should.test.js @@ -0,0 +1,544 @@ + +/** + * Module dependencies. + */ + +var should = require('../') + , assert = require('assert'); + +function err(fn, msg) { + try { + fn(); + should.fail('expected an error'); + } catch (err) { + should.equal(msg, err.message); + } +} + +module.exports = { + 'test .version': function(){ + should.version.should.match(/^\d+\.\d+\.\d+$/); + }, + + 'test double require': function(){ + require('should').should.equal(should); + }, + + 'test assertion': function(){ + 'test'.should.be.a.string; + should.equal('foo', 'foo'); + }, + + 'test true': function(){ + true.should.be.true; + false.should.not.be.true; + (1).should.not.be.true; + + err(function(){ + 'test'.should.be.true; + }, "expected 'test' to be true") + }, + + 'test ok': function(){ + true.should.be.ok; + false.should.not.be.ok; + (1).should.be.ok; + (0).should.not.be.ok; + + err(function(){ + ''.should.be.ok; + }, "expected '' to be truthy"); + + err(function(){ + 'test'.should.not.be.ok; + }, "expected 'test' to be falsey"); + }, + + 'test false': function(){ + false.should.be.false; + true.should.not.be.false; + (0).should.not.be.false; + + err(function(){ + ''.should.be.false; + }, "expected '' to be false") + }, + + 'test .expected and .actual': function(){ + try { + 'foo'.should.equal('bar'); + } catch (err) { + assert('foo' == err.actual, 'err.actual'); + assert('bar' == err.expected, 'err.expected'); + } + }, + + 'test arguments': function(){ + var args = (function(){ return arguments; })(1,2,3); + args.should.be.arguments; + [].should.not.be.arguments; + }, + + 'test .equal()': function(){ + var foo; + should.equal(undefined, foo); + }, + + 'test typeof': function(){ + 'test'.should.be.a('string'); + + err(function(){ + 'test'.should.not.be.a('string'); + }, "expected 'test' not to be a string"); + + err(function(){ + 'test'.should.not.be.a('string', 'foo'); + }, "expected 'test' not to be a string | foo"); + + (5).should.be.a('number'); + + err(function(){ + (5).should.not.be.a('number'); + }, "expected 5 not to be a number"); + + err(function(){ + (5).should.not.be.a('number', 'foo'); + }, "expected 5 not to be a number | foo"); + }, + + 'test instanceof': function(){ + function Foo(){} + new Foo().should.be.an.instanceof(Foo); + + err(function(){ + (3).should.an.instanceof(Foo); + }, "expected 3 to be an instance of Foo"); + + err(function(){ + (3).should.an.instanceof(Foo, 'foo'); + }, "expected 3 to be an instance of Foo | foo"); + }, + + 'test within(start, finish)': function(){ + (5).should.be.within(5, 10); + (5).should.be.within(3,6); + (5).should.be.within(3,5); + (5).should.not.be.within(1,3); + + err(function(){ + (5).should.not.be.within(4,6); + }, "expected 5 to not be within 4..6"); + + err(function(){ + (10).should.be.within(50,100); + }, "expected 10 to be within 50..100"); + + err(function(){ + (5).should.not.be.within(4,6, 'foo'); + }, "expected 5 to not be within 4..6 | foo"); + + err(function(){ + (10).should.be.within(50,100, 'foo'); + }, "expected 10 to be within 50..100 | foo"); + }, + + 'test above(n)': function(){ + (5).should.be.above(2); + (5).should.be.greaterThan(2); + (5).should.not.be.above(5); + (5).should.not.be.above(6); + + err(function(){ + (5).should.be.above(6); + }, "expected 5 to be above 6"); + + err(function(){ + (10).should.not.be.above(6); + }, "expected 10 to be below 6"); + + err(function(){ + (5).should.be.above(6, 'foo'); + }, "expected 5 to be above 6 | foo"); + + err(function(){ + (10).should.not.be.above(6, 'foo'); + }, "expected 10 to be below 6 | foo"); + }, + + 'test below(n)': function(){ + (2).should.be.below(5); + (2).should.be.lessThan(5); + (5).should.not.be.below(5); + (6).should.not.be.below(5); + + err(function(){ + (6).should.be.below(5); + }, "expected 6 to be below 5"); + + err(function(){ + (6).should.not.be.below(10); + }, "expected 6 to be above 10"); + + err(function(){ + (6).should.be.below(5, 'foo'); + }, "expected 6 to be below 5 | foo"); + + err(function(){ + (6).should.not.be.below(10, 'foo'); + }, "expected 6 to be above 10 | foo"); + }, + + 'test match(regexp)': function(){ + 'foobar'.should.match(/^foo/) + 'foobar'.should.not.match(/^bar/) + + err(function(){ + 'foobar'.should.match(/^bar/i) + }, "expected 'foobar' to match /^bar/i"); + + err(function(){ + 'foobar'.should.not.match(/^foo/i) + }, "expected 'foobar' not to match /^foo/i"); + + err(function(){ + 'foobar'.should.match(/^bar/i, 'foo') + }, "expected 'foobar' to match /^bar/i | foo"); + + err(function(){ + 'foobar'.should.not.match(/^foo/i, 'foo') + }, "expected 'foobar' not to match /^foo/i | foo"); + }, + + 'test length(n)': function(){ + 'test'.should.have.length(4); + 'test'.should.not.have.length(3); + [1,2,3].should.have.length(3); + + err(function(){ + (4).should.have.length(3); + }, 'expected 4 to have a property \'length\''); + + err(function(){ + 'asd'.should.not.have.length(3); + }, "expected 'asd' to not have a length of 3"); + + err(function(){ + 'asd'.should.have.length(4, 'foo'); + }, "expected 'asd' to have a length of 4 but got 3 | foo"); + + err(function(){ + 'asd'.should.not.have.length(3, 'foo'); + }, "expected 'asd' to not have a length of 3 | foo"); + + }, + + 'test eql(val)': function(){ + 'test'.should.eql('test'); + ({ foo: 'bar' }).should.eql({ foo: 'bar' }); + (1).should.eql(1); + '4'.should.not.eql(4); + + err(function(){ + (4).should.eql(3); + }, 'expected 4 to equal 3'); + + err(function(){ + (4).should.eql(3, "foo"); + }, 'expected 4 to equal 3 | foo'); + + err(function(){ + (3).should.not.eql(3, "foo"); + }, 'expected 3 to not equal 3 | foo'); + }, + + 'test .json': function(){ + var req = { + headers: { + 'content-type': 'application/json' + } + }; + + req.should.be.json; + + var req = { + headers: { + 'content-type': 'application/json; charset=utf-8' + } + }; + + req.should.be.json; + }, + + 'test equal(val)': function(){ + 'test'.should.equal('test'); + (1).should.equal(1); + + err(function(){ + (4).should.equal(3); + }, 'expected 4 to equal 3'); + + err(function(){ + '4'.should.equal(4); + }, "expected '4' to equal 4"); + + err(function(){ + (3).should.equal(4, "foo"); + }, "expected 3 to equal 4 | foo"); + + err(function(){ + (4).should.not.equal(4, "foo"); + }, "expected 4 to not equal 4 | foo"); + }, + + 'test empty': function(){ + ''.should.be.empty; + [].should.be.empty; + ({ length: 0 }).should.be.empty; + + err(function(){ + ({}).should.be.empty; + }, 'expected {} to have a property \'length\''); + + err(function(){ + 'asd'.should.be.empty; + }, "expected 'asd' to be empty"); + + err(function(){ + ''.should.not.be.empty; + }, "expected '' not to be empty"); + }, + + 'test property(name)': function(){ + 'test'.should.have.property('length'); + (4).should.not.have.property('length'); + + err(function(){ + 'asd'.should.have.property('foo'); + }, "expected 'asd' to have a property 'foo'"); + + err(function(){ + 'asd'.should.have.property('foo', undefined, 'foo'); + }, "expected 'asd' to have a property 'foo' | foo"); + + err(function(){ + 'asd'.should.not.have.property('length', undefined, 'foo'); + }, "expected 'asd' to not have a property 'length' | foo"); + }, + + 'test property(name, val)': function(){ + 'test'.should.have.property('length', 4); + 'asd'.should.have.property('constructor', String); + + err(function(){ + 'asd'.should.have.property('length', 4); + }, "expected 'asd' to have a property 'length' of 4, but got 3"); + + err(function(){ + 'asd'.should.not.have.property('length', 3); + }, "expected 'asd' to not have a property 'length' of 3"); + + err(function(){ + 'asd'.should.not.have.property('foo', 3); + }, "'asd' has no property 'foo'"); + + err(function(){ + 'asd'.should.have.property('constructor', Number); + }, "expected 'asd' to have a property 'constructor' of [Function: Number], but got [Function: String]"); + + err(function(){ + 'asd'.should.have.property('length', 4, 'foo'); + }, "expected 'asd' to have a property 'length' of 4, but got 3 | foo"); + + err(function(){ + 'asd'.should.not.have.property('length', 3, 'foo'); + }, "expected 'asd' to not have a property 'length' of 3 | foo"); + + err(function(){ + 'asd'.should.not.have.property('foo', 3, 'foo'); + }, "'asd' has no property 'foo' | foo"); + + err(function(){ + 'asd'.should.have.property('constructor', Number, 'foo'); + }, "expected 'asd' to have a property 'constructor' of [Function: Number], but got [Function: String] | foo"); + }, + + 'test ownProperty(name)': function(){ + 'test'.should.have.ownProperty('length'); + 'test'.should.haveOwnProperty('length'); + ({ length: 12 }).should.have.ownProperty('length'); + + err(function(){ + ({ length: 12 }).should.not.have.ownProperty('length'); + }, "expected { length: 12 } to not have own property 'length'"); + + err(function(){ + ({ length: 12 }).should.not.have.ownProperty('length', 'foo'); + }, "expected { length: 12 } to not have own property 'length' | foo"); + + err(function(){ + ({ length: 12 }).should.have.ownProperty('foo', 'foo'); + }, "expected { length: 12 } to have own property 'foo' | foo"); + }, + + 'test include() with string': function(){ + 'foobar'.should.include('bar'); + 'foobar'.should.include('foo'); + 'foobar'.should.not.include('baz'); + + err(function(){ + 'foobar'.should.include('baz'); + }, "expected 'foobar' to include 'baz'"); + + err(function(){ + 'foobar'.should.not.include('bar'); + }, "expected 'foobar' to not include 'bar'"); + + err(function(){ + 'foobar'.should.include('baz', 'foo'); + }, "expected 'foobar' to include 'baz' | foo"); + + err(function(){ + 'foobar'.should.not.include('bar', 'foo'); + }, "expected 'foobar' to not include 'bar' | foo"); + }, + + 'test include() with array': function(){ + ['foo', 'bar'].should.include('foo'); + ['foo', 'bar'].should.include('foo'); + ['foo', 'bar'].should.include('bar'); + [1,2].should.include(1); + ['foo', 'bar'].should.not.include('baz'); + ['foo', 'bar'].should.not.include(1); + + err(function(){ + ['foo'].should.include('bar'); + }, "expected [ 'foo' ] to include 'bar'"); + + err(function(){ + ['bar', 'foo'].should.not.include('foo'); + }, "expected [ 'bar', 'foo' ] to not include 'foo'"); + + err(function(){ + ['foo'].should.include('bar', 'foo'); + }, "expected [ 'foo' ] to include 'bar' | foo"); + + err(function(){ + ['bar', 'foo'].should.not.include('foo', 'foo'); + }, "expected [ 'bar', 'foo' ] to not include 'foo' | foo"); + }, + + 'test includeEql() with array': function(){ + [['foo'], ['bar']].should.includeEql(['foo']); + [['foo'], ['bar']].should.includeEql(['bar']); + [['foo'], ['bar']].should.not.includeEql(['baz']); + [].should.not.includeEql(['baz']); + + err(function(){ + [['foo']].should.includeEql(['bar']); + }, "expected [ [ 'foo' ] ] to include an object equal to [ 'bar' ]"); + + err(function(){ + [['foo']].should.not.includeEql(['foo']); + }, "expected [ [ 'foo' ] ] to not include an object equal to [ 'foo' ]"); + + err(function(){ + [['foo']].should.includeEql(['bar'], 'foo'); + }, "expected [ [ 'foo' ] ] to include an object equal to [ 'bar' ] | foo"); + + err(function(){ + [['foo']].should.not.includeEql(['foo'], 'foo'); + }, "expected [ [ 'foo' ] ] to not include an object equal to [ 'foo' ] | foo"); + }, + + 'test keys(array)': function(){ + ({ foo: 1 }).should.have.keys(['foo']); + ({ foo: 1, bar: 2 }).should.have.keys(['foo', 'bar']); + ({ foo: 1, bar: 2 }).should.have.keys('foo', 'bar'); + + err(function(){ + ({ foo: 1 }).should.have.keys(); + }, "keys required"); + + err(function(){ + ({ foo: 1 }).should.have.keys([]); + }, "keys required"); + + err(function(){ + ({ foo: 1 }).should.not.have.keys([]); + }, "keys required"); + + err(function(){ + ({ foo: 1 }).should.have.keys(['bar']); + }, "expected { foo: 1 } to have key 'bar'"); + + err(function(){ + ({ foo: 1 }).should.have.keys(['bar', 'baz']); + }, "expected { foo: 1 } to have keys 'bar', and 'baz'"); + + err(function(){ + ({ foo: 1 }).should.have.keys(['foo', 'bar', 'baz']); + }, "expected { foo: 1 } to have keys 'foo', 'bar', and 'baz'"); + + err(function(){ + ({ foo: 1 }).should.not.have.keys(['foo']); + }, "expected { foo: 1 } to not have key 'foo'"); + + err(function(){ + ({ foo: 1 }).should.not.have.keys(['foo']); + }, "expected { foo: 1 } to not have key 'foo'"); + + err(function(){ + ({ foo: 1, bar: 2 }).should.not.have.keys(['foo', 'bar']); + }, "expected { foo: 1, bar: 2 } to not have keys 'foo', and 'bar'"); + }, + + 'test chaining': function(){ + var user = { name: 'tj', pets: ['tobi', 'loki', 'jane', 'bandit'] }; + user.should.have.property('pets').with.lengthOf(4); + + err(function(){ + user.should.have.property('pets').with.lengthOf(5); + }, "expected [ 'tobi', 'loki', 'jane', 'bandit' ] to have a length of 5 but got 4"); + + user.should.be.a('object').and.have.property('name', 'tj'); + }, + + 'test throw()': function(){ + (function(){}).should.not.throw(); + (function(){ throw new Error('fail') }).should.throw(); + + err(function(){ + (function(){}).should.throw(); + }, 'expected an exception to be thrown'); + + err(function(){ + (function(){ + throw new Error('fail'); + }).should.not.throw(); + }, 'expected no exception to be thrown, got "fail"'); + }, + + 'test throw() with regex message': function(){ + (function(){ throw new Error('fail'); }).should.throw(/fail/); + + err(function(){ + (function(){}).should.throw(/fail/); + }, 'expected an exception to be thrown'); + + err(function(){ + (function(){ throw new Error('error'); }).should.throw(/fail/); + }, "expected an exception to be thrown with a message matching /fail/, but got 'error'"); + }, + + 'test throw() with string message': function(){ + (function(){ throw new Error('fail'); }).should.throw('fail'); + + err(function(){ + (function(){}).should.throw('fail'); + }, 'expected an exception to be thrown'); + + err(function(){ + (function(){ throw new Error('error'); }).should.throw('fail'); + }, "expected an exception to be thrown with a message matching 'fail', but got 'error'"); + } +}; diff --git a/node_modules/socket.io-client/.npmignore b/node_modules/socket.io-client/.npmignore new file mode 100644 index 0000000..c27cb50 --- /dev/null +++ b/node_modules/socket.io-client/.npmignore @@ -0,0 +1,2 @@ +test/node_modules +support diff --git a/node_modules/socket.io-client/History.md b/node_modules/socket.io-client/History.md new file mode 100644 index 0000000..675f656 --- /dev/null +++ b/node_modules/socket.io-client/History.md @@ -0,0 +1,182 @@ + +0.9.4 / 2012-04-01 +================== + + * Fixes polling loop upon reconnect advice (fixes #438). + +0.9.3 / 2012-03-28 +================== + + * Fix XHR.check, which was throwing an error transparently and causing non-IE browsers to fall back to JSONP [mikito] + * Fixed forced disconnect on window close [zzzaaa] + +0.9.2 / 2012-03-13 +================== + + * Transport order set by "options" [zzzaaa] + +0.9.1-1 / 2012-03-02 +==================== + + * Fixed active-x-obfuscator NPM dependency. + +0.9.1 / 2012-03-02 +================== + + * Misc corrections. + * Added warning within Firefox about webworker test in test runner. + * Update ws dependency [einaros] + * Implemented client side heartbeat checks. [felixge] + * Improved Firewall support with ActiveX obfuscation. [felixge] + * Fixed error handling during connection process. [Outsideris] + +0.9.0 / 2012-02-26 +================== + + * Added DS_Store to gitignore. + * Updated depedencies. + * Bumped uglify + * Tweaking code so it doesn't throw an exception when used inside a WebWorker in Firefox + * Do not rely on Array.prototype.indexOf as it breaks with pages that use the Prototype.js library. + * Windows support landed + * Use @einaros ws module instead of the old crap one + * Fix for broken closeTimeout and 'IE + xhr' goes into infinite loop on disconnection + * Disabled reconnection on error if reconnect option is set to false + * Set withCredentials to true before xhr to fix authentication + * Clears the timeout from reconnection attempt when there is a successful or failed reconnection. + This fixes the issue of setTimeout's carrying over from previous reconnection + and changing (skipping) values of self.reconnectionDelay in the newer reconnection. + * Removed decoding of parameters when chunking the query string. + This was used later on to construct the url to post to the socket.io server + for connection and if we're adding custom parameters of our own to this url + (for example for OAuth authentication) they were being sent decoded, which is wrong. + +0.8.7 / 2011-11-05 +================== + + * Bumped client + +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-client/Makefile b/node_modules/socket.io-client/Makefile new file mode 100644 index 0000000..f2d2f41 --- /dev/null +++ b/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-client/README.md b/node_modules/socket.io-client/README.md new file mode 100644 index 0000000..cdb7715 --- /dev/null +++ b/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-client/bin/builder.js b/node_modules/socket.io-client/bin/builder.js new file mode 100755 index 0000000..8870e51 --- /dev/null +++ b/node_modules/socket.io-client/bin/builder.js @@ -0,0 +1,281 @@ +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var fs = require('fs') + , socket = require('../lib/io') + , uglify = require('uglify-js') + , activeXObfuscator = require('active-x-obfuscator'); + +/** + * 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; + }); + } + + code = activeXObfuscator(code); + + // 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-client/dist/WebSocketMain.swf b/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-client/dist/WebSocketMain.swf differ diff --git a/node_modules/socket.io-client/dist/WebSocketMainInsecure.swf b/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-client/dist/WebSocketMainInsecure.swf differ diff --git a/node_modules/socket.io-client/dist/socket.io.js b/node_modules/socket.io-client/dist/socket.io.js new file mode 100644 index 0000000..eec5f82 --- /dev/null +++ b/node_modules/socket.io-client/dist/socket.io.js @@ -0,0 +1,3787 @@ +/*! Socket.IO.js build:0.9.4, 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.9.4'; + + /** + * 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]] = 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 window[(['Active'].concat('Object').join('X'))]('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) { + + 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.socket.connected || this.socket.connecting || this.socket.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) { + this.socket.setHeartbeatTimeout(); + + if (packet.type == 'heartbeat') { + return this.onHeartbeat(); + } + + if (packet.type == 'connect' && packet.endpoint == '') { + this.onConnect(); + } + + if (packet.type == 'error' && packet.advice == 'reconnect') { + this.open = false; + } + + 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, 'unload', 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.withCredentials = 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 = transports ? io.util.intersect( + transports.split(',') + , self.options.transports + ) : self.options.transports; + + self.setHeartbeatTimeout(); + + 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.transports); + + self.once('connect', function (){ + clearTimeout(self.connectTimeoutTimer); + + fn && typeof fn == 'function' && fn(); + }); + }); + + return this; + }; + + /** + * Clears and sets a new heartbeat timeout using the value given by the + * server during the handshake. + * + * @api private + */ + + Socket.prototype.setHeartbeatTimeout = function () { + clearTimeout(this.heartbeatTimeoutTimer); + + var self = this; + this.heartbeatTimeoutTimer = setTimeout(function () { + self.transport.onClose(); + }, this.heartbeatTimeout); + }; + + /** + * 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 || this.connecting) { + 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; + clearTimeout(this.heartbeatTimeoutTimer); + }; + + /** + * 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.connecting)) { + this.disconnect(); + if (this.options.reconnect) { + 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 + , wasConnecting = this.connecting; + + this.connected = false; + this.connecting = false; + this.open = false; + + if (wasConnected || wasConnecting) { + this.transport.close(); + this.transport.clearTimeouts(); + if (wasConnected) { + 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); + } + + clearTimeout(self.reconnectionTimer); + + 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[(['Active'].concat('Object').join('X'))]!=D){try{var ad=new window[(['Active'].concat('Object').join('X'))](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 { + var request = io.util.request(xdomain), + usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), + socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), + isXProtocol = (socketProtocol != global.location.protocol); + if (request && !(usesXDomReq && isXProtocol)) { + return true; + } + } catch(e) {} + + return false; + }; + + /** + * Check if the XHR transport supports cross 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 Ac...eX `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 window[(['Active'].concat('Object').join('X'))]('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 `Ac...eXObject` implementation. + * + * @return {Boolean} + * @api public + */ + + HTMLFile.check = function () { + if (typeof window != "undefined" && (['Active'].concat('Object').join('X')) in window){ + try { + var a = new window[(['Active'].concat('Object').join('X'))]('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; + this.onerror = empty; + self.onData(this.responseText); + self.get(); + }; + + function onerror () { + self.onClose(); + }; + + this.xhr = this.request(); + + if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { + this.xhr.onload = onload; + this.xhr.onerror = onerror; + } 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 = this.xhr.onerror = 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(''; +html += '
'; +html += '
'; +html += '
Upload File
'; +html += '
Want to upload multiple files at once? Please upgrade to the latest Flash Player, then reload this page. For some reason our Flash based uploader did not load, so you are currently using our single file uploader.
'; +html += spacer(1,20) + '
'; +var url = zero_client.targetURL; +if (url.indexOf('?') > -1) url += '&'; else url += '?'; +url += 'format=jshtml&onafter=' + escape('window.parent.upload_basic_finish(response);'); +Debug.trace('upload', "Prepping basic upload: " + url); +html += '
'; +html += '
'; +html += '
'; +html += '

'; +html += ''; +html += ''; +html += ''; +html += '
' + large_icon_button('x', 'Cancel', "hide_popup_dialog()") + ' ' + large_icon_button('page_white_get.png', 'Upload', "upload_basic_go()") + '
'; +html += '
'; +html += ''; +html += '
'; +html += ''; +session.hooks.keys[ESC_KEY] = 'hide_popup_dialog'; +show_popup_dialog(528, 200, html); +} +function upload_basic_go() { +$('f_upload_basic').submit(); +$('d_upload_form').hide(); +$('d_upload_progress').show(); +} +function upload_basic_finish(response) { +Debug.trace('upload', "Basic upload complete: " + dumper(response)); +setTimeout( 'upload_basic_finish_2()', 100 ); +} +function upload_basic_finish_2() { +$('i_upload_basic').src = 'blank.html'; +setTimeout( 'upload_basic_finish_3()', 100 ); +} +function upload_basic_finish_3() { +hide_popup_dialog(); +delete session.progress; +show_progress_dialog( 0, 'Finishing Upload...', true ); +fire_callback( session.upload_callback ); +} +function upload_destroy() { +if (zero_client) { +zero_client.destroy(); +delete ZeroUpload.clients[ zero_client.id ]; +zero_client = null; +} +} +function prep_upload(dom_id, url, callback, types) { +session.upload_callback = callback; +if (url) { +if (url.indexOf('?') > -1) url += '&'; else url += '?'; +url += 'session=' + session.cookie.get('effect_session_id'); +} +upload_destroy(); +zero_client = new ZeroUpload.Client(); +if (url) zero_client.setURL( url ); +zero_client.setHandCursor( true ); +if (types) zero_client.setFileTypes( types[0], types[1] ); +zero_client.addEventListener( 'queueStart', uploadQueueStart ); +zero_client.addEventListener( 'fileStart', uploadFileStart ); +zero_client.addEventListener( 'progress', uploadProgress ); +zero_client.addEventListener( 'fileComplete', uploadFileComplete ); +zero_client.addEventListener( 'queueComplete', uploadQueueComplete ); +zero_client.addEventListener( 'error', uploadError ); +zero_client.addEventListener( 'debug', function(client, eventName, args) { +Debug.trace('upload', "Caught event: " + eventName); +} ); +if (dom_id) { +Debug.trace('upload', "Gluing ZeroUpload to: " + dom_id); +zero_client.glue( dom_id ); +} +} +Class.create( 'Debug', { +__static: { +enabled: false, +categories: { all: 1 }, +buffer: [], +max_rows: 5000, +win: null, +ie: !!navigator.userAgent.match(/MSIE/), +ie6: !!navigator.userAgent.match(/MSIE\D+6/), +init: function() { +Debug.enabled = true; +Debug.trace( 'debug', 'Debug log start' ); +var html = '

'; +if (Debug.ie) { +setTimeout( function() { +document.body.insertAdjacentHTML('beforeEnd', +'
' + html + '
' +); +}, 1000 ); +} +else { +var div = document.createElement('DIV'); +div.id = 'd_debug'; +div.setAttribute('id', 'd_debug'); +div.style.position = Debug.ie6 ? 'absolute' : 'fixed'; +div.style.zIndex = '101'; +div.style.left = '0px'; +div.style.top = '0px'; +div.style.width = '100%'; +div.innerHTML = html; +document.getElementsByTagName('body')[0].appendChild(div); +} +}, +show: function() { +if (!Debug.win || Debug.win.closed) { +Debug.trace('debug', "Opening debug window"); +Debug.win = window.open( '', 'DebugWindow', 'width=600,height=500,menubar=no,resizable=yes,scrollbars=yes,location=no,status=no,toolbar=no,directories=no' ); +if (!Debug.win) return alert("Failed to open window. Popup blocker maybe?"); +var doc = Debug.win.document; +doc.open(); +doc.writeln( 'Debug Log' ); +doc.writeln( '
' ); +doc.writeln( '
' ); +doc.writeln( '
' ); +doc.writeln( '' ); +doc.writeln( '' ); +doc.writeln( '
' ); +doc.writeln( '' ); +doc.close(); +} +Debug.win.focus(); +}, +console_execute: function() { +var cmd = Debug.win.document.getElementById('fe_command'); +if (cmd.value.length) { +Debug.trace( 'console', cmd.value ); +try { +Debug.trace( 'console', '' + eval(cmd.value) ); +} +catch (e) { +Debug.trace( 'error', 'JavaScript Interpreter Exception: ' + e.toString() ); +} +} +}, +get_time_stamp: function(now) { +var date = new Date( now * 1000 ); +var hh = date.getHours(); if (hh < 10) hh = "0" + hh; +var mi = date.getMinutes(); if (mi < 10) mi = "0" + mi; +var ss = date.getSeconds(); if (ss < 10) ss = "0" + ss; +var sss = '' + date.getMilliseconds(); while (sss.length < 3) sss = "0" + sss; +return '' + hh + ':' + mi + ':' + ss + '.' + sss; +}, +refresh_console: function() { +if (!Debug.win || Debug.win.closed) return; +var div = Debug.win.document.getElementById('d_debug_log'); +if (div) { +var row = null; +while ( row = Debug.buffer.shift() ) { +var time_stamp = Debug.get_time_stamp(row.time); +var msg = row.msg; +msg = msg.replace(/\t/g, "    "); +msg = msg.replace(//g, ">"); +msg = msg.replace(/\n/g, "
\n"); +var html = ''; +var sty = 'float:left; font-family: Consolas, Courier, mono; font-size: 12px; cursor:default; margin-right:10px; margin-bottom:1px; padding:2px;'; +html += '
' + time_stamp + '
'; +html += '
' + row.cat + '
'; +html += '
' + msg + '
'; +html += '
'; +var chunk = Debug.win.document.createElement('DIV'); +chunk.style['float'] = 'none'; +chunk.innerHTML = html; +div.appendChild(chunk); +} +var cmd = Debug.win.document.getElementById('fe_command'); +cmd.focus(); +} +Debug.dirty = 0; +Debug.win.scrollTo(0, 99999); +}, +hires_time_now: function() { +var now = new Date(); +return ( now.getTime() / 1000 ); +}, +trace: function(cat, msg) { +if (arguments.length == 1) { +msg = cat; +cat = 'debug'; +} +if (Debug.categories.all || Debug.categories[cat]) { +Debug.buffer.push({ cat: cat, msg: msg, time: Debug.hires_time_now() }); +if (Debug.buffer.length > Debug.max_rows) Debug.buffer.shift(); +if (!Debug.dirty) { +Debug.dirty = 1; +setTimeout( 'Debug.refresh_console();', 1 ); +} +} +} +} +} ); +var session = { +inited: false, +api_mod_cache: {}, +query: parseQueryString( ''+location.search ), +cookie: new CookieTree({ path: '/effect/' }), +storage: {}, +storage_dirty: false, +hooks: { +keys: {} +}, +username: '', +em_width: 11, +audioResourceMatch: /\.mp3$/i, +imageResourceMatch: /\.(jpe|jpeg|jpg|png|gif)$/i, +textResourceMatch: /\.xml$/i, +movieResourceMatch: /\.(flv|mp4|mp4v|mov|3gp|3g2)$/i, +imageResourceMatchString: '\.(jpe|jpeg|jpg|png|gif)$' +}; +session.debug = session.query.debug ? true : false; +var page_manager = null; +var preload_icons = []; +var preload_images = [ +'loading.gif', +'aquaprogressbar.gif', +'aquaprogressbar_bkgnd.gif' +]; +function get_base_url() { +return protocol + '://' + location.hostname + session.config.BaseURI; +} +function effect_init() { +if (session.inited) return; +session.inited = true; +assert( window.config, "Config not loaded" ); +session.config = window.config; +Debug.trace("Starting up"); +rendering_page = false; +preload(); +window.$R = {}; +for (var key in config.RegExpShortcuts) { +$R[key] = new RegExp( config.RegExpShortcuts[key] ); +} +ww_precalc_font("body", "effect_precalc_font_finish"); +page_manager = new Effect.PageManager( config.Pages.Page ); +var session_id = session.cookie.get('effect_session_id'); +if (session_id && session_id.match(/^login/)) { +do_session_recover(); +} +else { +show_default_login_status(); +Nav.init(); +} +Blog.search({ +stag: 'sidebar_docs', +limit: 20, +title_only: true, +sort_by: 'seq', +sort_dir: -1, +target: 'd_sidebar_documents', +outer_div_class: 'sidebar_blog_row', +title_class: 'sidebar_blog_title', +after: '' +}); +Blog.search({ +stag: 'sidebar_tutorials', +limit: 5, +title_only: true, +sort_by: 'seq', +sort_dir: -1, +target: 'd_sidebar_tutorials', +outer_div_class: 'sidebar_blog_row', +title_class: 'sidebar_blog_title', +after: '' +}); +Blog.search({ +stag: 'sidebar_plugins', +limit: 5, +title_only: true, +sort_by: 'seq', +sort_dir: -1, +target: 'd_sidebar_plugins', +outer_div_class: 'sidebar_blog_row', +title_class: 'sidebar_blog_title', +after: '' +}); +$('fe_search_bar').onkeydown = delay_onChange_input_text; +user_storage_idle(); +} +function effect_precalc_font_finish(width, height) { +session.em_width = width; +} +function preload() { +for (var idx = 0, len = preload_icons.length; idx < len; idx++) { +var url = images_uri + '/icons/' + preload_icons[idx] + '.gif'; +preload_icons[idx] = new Image(); +preload_icons[idx].src = url; +} +for (var idx = 0, len = preload_images.length; idx < len; idx++) { +var url = images_uri + '/' + preload_images[idx]; +preload_images[idx] = new Image(); +preload_images[idx].src = url; +} +} +function $P(id) { +if (!id) id = page_manager.current_page_id; +var page = page_manager.find(id); +assert( !!page, "Failed to locate page: " + id ); +return page; +} +function get_pref(name) { +if (!session.user || !session.user.Preferences) return alert("ASSERT FAILURE! Tried to lookup pref " + name + " and user is not yet loaded!"); +return session.user.Preferences[name]; +} +function get_bool_pref(name) { +return (get_pref(name) == 1); +} +function set_pref(name, value) { +session.user.Preferences[name] = value; +} +function set_bool_pref(name, value) { +set_pref(name, value ? '1' : '0'); +} +function save_prefs() { +var prefs_to_save = {}; +if (arguments.length) { +for (var idx = 0, len = arguments.length; idx < len; idx++) { +var key = arguments[idx]; +prefs_to_save[key] = get_pref(key); +} +} +else prefs_to_save = session.user.Preferences; +effect_api_mod_touch('user_get'); +effect_api_send('user_update', { +Username: session.username, +Preferences: prefs_to_save +}, 'save_prefs_2'); +} +function save_prefs_2(response) { +do_message('success', 'Preferences saved.'); +} + +function get_full_name(username) { +var user = session.users[username]; +if (!user) return username; +return user.FullName; +} +function get_buddy_icon_url(username, size) { +var mod = session.api_mod_cache.get_buddy_icon || 0; +if (!size) size = 32; +var url = '/effect/api/get_buddy_icon?username='+username + '&mod=' + mod + '&size=' + size; +return url; +} +function get_buddy_icon_display(username, show_icon, show_name) { +if ((typeof(show_icon) == 'undefined') && get_bool_pref('show_user_icons')) show_icon = 1; +if ((typeof(show_name) == 'undefined') && get_bool_pref('show_user_names')) show_name = 1; +var html = ''; +if (show_icon) html += ''; +if (show_icon && show_name) html += '
'; +if (show_name) html += username; +return html; +} +function do_session_recover() { +session.hooks.after_error = 'do_logout'; +effect_api_send('session_recover', {}, 'do_login_2', { _from_recover: 1 } ); +} +function require_login() { +if (session.user) return true; +Debug.trace('Page requires login, showing login page'); +session.nav_after_login = Nav.currentAnchor(); +setTimeout( function() { +Nav.go( 'Login' ); +}, 1 ); +return false; +} +function popup_window(url, name) { +if (!url) url = ''; +if (!name) name = ''; +var win = window.open(url, name); +if (!win) return alert('Failed to open popup window. If you have a popup blocker, please disable it for this website and try again.'); +return win; +} +function do_login_prompt() { +hide_popup_dialog(); +delete session.progress; +if (!session.temp_password) session.temp_password = ''; +if (!session.username) session.username = ''; +var temp_username = session.open_id || session.username || ''; +var html = ''; +html += '
'; +html += '
'; +html += '
Effect Developer Login
'; +html += '
'; +html += '
Effect Username  or  '+icon('openid', 'OpenID', 'popup_window(\'http://openid.net/\')', 'What is OpenID?')+'


'; +html += '
'; +html += '
'; +html += '

'; +html += ''; +html += ''; +html += ''; +html += '
' + large_icon_button('x', 'Cancel', "clear_login()") + ' ' + large_icon_button('check', 'Login', 'do_login()') + '
'; +html += '
'; +html += ''; +session.hooks.keys[ENTER_KEY] = 'do_login'; +session.hooks.keys[ESC_KEY] = 'clear_login'; +safe_focus( 'fe_username' ); +show_popup_dialog(450, 225, html); +} +function do_openid_reg(title, auto_login_button) { +hide_popup_dialog(); +delete session.progress; +if (!title) title = 'Register Account Using OpenID'; +if (typeof(auto_login_button) == 'undefined') auto_login_button = 1; +var html = ''; +html += '
'; +html += '
'; +html += '
'+title+'
'; +html += '
'; +html += '
'+icon('openid', 'Enter Your OpenID URL:')+'
'; +if (auto_login_button) html += '


'; +html += '
'; +html += '

'; +html += ''; +html += ''; +html += ''; +html += '
' + large_icon_button('x', 'Cancel', "hide_popup_dialog()") + ' ' + large_icon_button('check', title.match(/login/i) ? 'Login' : 'Register', 'do_openid_login()') + '
'; +html += '
'; +html += ''; +session.hooks.keys[ENTER_KEY] = 'do_openid_login'; +session.hooks.keys[ESC_KEY] = 'hide_popup_dialog'; +safe_focus( 'fe_username' ); +show_popup_dialog(450, 225, html); +} +function do_login_prompt_2() { +hide_popup_dialog(); +delete session.progress; +if (!session.temp_password) session.temp_password = ''; +if (!session.username) session.username = ''; +var html = ''; +html += '
'; +html += '
'; +html += '
Enter Your Password
'; +html += '
'; +html += '
Password:


'; +html += '
'; +html += '
'; +html += '

'; +html += ''; +html += ''; +html += ''; +html += '
' + large_icon_button('x', 'Cancel', "clear_login()") + ' ' + large_icon_button('check', 'Login', 'do_effect_login()') + '
'; +html += '
'; +html += ''; +session.hooks.keys[ENTER_KEY] = 'do_effect_login'; +session.hooks.keys[ESC_KEY] = 'clear_login'; +safe_focus( 'fe_lp_password' ); +show_popup_dialog(450, 225, html); +} +function clear_login() { +hide_popup_dialog(); +Nav.prev(); +} +function do_login() { +if ($('fe_username').value.match(/^\w+$/)) { +session.username = $('fe_username').value; +session.auto_login = $('fe_auto_login').checked; +do_login_prompt_2(); +return; +} +else { +do_openid_login(); +} +} +function do_openid_login() { +if (!$('fe_username').value) return; +session.openid_win = popup_window(''); +if (!session.openid_win) return; +session.open_id = $('fe_username').value; +session.auto_login = $('fe_auto_login') && $('fe_auto_login').checked; +hide_popup_dialog(); +show_progress_dialog(1, "Logging in..."); +session.hooks.before_error = 'close_openid_window'; +session.hooks.after_error = 'do_login_prompt'; +effect_api_send('openid_login', { +OpenID: session.open_id, +Infinite: session.auto_login ? 1 : 0 +}, 'do_openid_login_2'); +} +function close_openid_window() { +if (session.openid_win) { +session.openid_win.close(); +delete session.openid_win; +} +} +function do_openid_login_2(response) { +if (response.CheckURL) { +Debug.trace('openid', "Redirecting popup window to OpenID Check URL: " + response.CheckURL); +show_progress_dialog(1, "Waiting for popup window...", false, ['x', 'Cancel', 'do_login_prompt()']); +session.openid_win.location = response.CheckURL; +session.openid_win.focus(); +} +} +function receive_openid_response(iframe_response) { +var response = deep_copy_object(iframe_response); +Debug.trace('openid', "Received OpenID Response: " + dumper(response)); +hide_popup_dialog(); +if (response.Code) { +close_openid_window(); +return do_error( response.Description ); +} +delete session.hooks.before_error; +delete session.hooks.after_error; +if (response.SessionID) { +session.cookie.set( 'effect_session_id', response.SessionID ); +session.cookie.save(); +} +switch (response.Action) { +case 'popup': +show_progress_dialog(1, "Waiting for popup window...", false, ['x', 'Cancel', 'do_login_prompt()']); +Debug.trace('openid', "Redirecting popup window to OpenID Setup URL: " + response.SetupURL); +session.openid_win.location = response.SetupURL; +session.openid_win.focus(); +break; +case 'login': +close_openid_window(); +do_login_2(response); +break; +case 'register': +if (!response.Info) response.Info = {}; +close_openid_window(); +Debug.trace('openid', 'Original OpenID: ' + response.OpenID_Login); +Debug.trace('openid', 'Clean OpenID: ' + response.OpenID_Unique); +Debug.trace('openid', 'Registration Info: ' + dumper(response.Info)); +session.prereg = response.Info; +session.prereg.open_id_login = response.OpenID_Login; +session.prereg.open_id = response.OpenID_Unique; +if (session.user) { +if (!session.user.OpenIDs) session.user.OpenIDs = {}; +if (!session.user.OpenIDs.OpenID) session.user.OpenIDs.OpenID = []; +var dupe = find_object( session.user.OpenIDs.OpenID, { Unique: session.prereg.open_id } ); +if (dupe) return do_error("That OpenID is already registered and attached to your account. No need to add it again."); +session.user.OpenIDs.OpenID.push({ +Login: session.prereg.open_id_login, +Unique: session.prereg.open_id +}); +setTimeout( function() { +Nav.go('MyAccount', true); +do_message('success', 'Added new OpenID URL to account.'); +}, 1 ); +} +else { +setTimeout( function() { Nav.go('CreateAccount', true); }, 1 ); +} +break; +} +} +function do_effect_login() { +var password = $('fe_lp_password').value; +session.auto_login = $('fe_auto_login').checked; +hide_popup_dialog(); +show_progress_dialog(1, "Logging in..."); +session.hooks.after_error = 'do_login_prompt'; +effect_api_send('user_login', { +Username: session.username, +Password: password, +Infinite: session.auto_login ? 1 : 0 +}, 'do_login_2'); +} +function do_logout() { +effect_api_send('user_logout', {}, 'do_logout_2'); +} +function do_logout_2(response) { +hide_popup_dialog(); +show_default_login_status(); +delete session.hooks.after_error; +delete session.cookie.tree.effect_session_id; +session.cookie.save(); +session.storage = {}; +session.storage_dirty = false; +delete session.user; +delete session.first_login; +var old_username = session.username; +session.username = ''; +if (Nav.inited) { +Nav.go('Main'); +if (old_username) $GR.growl('success', "Logged out of account: " + old_username); +} +else { +Nav.init(); +} +} +function do_login_2(response, tx) { +if (response.FirstLogin) session.first_login = 1; +if (response.User.UserStorage) { +Debug.trace('Recovering site storage blob: session.storage = ' + response.User.UserStorage + ';'); +try { +eval( 'session.storage = ' + response.User.UserStorage + ';' ); +} +catch (e) { +Debug.trace("SITE STORAGE RECOVERY FAILED: " + e); +session.storage = {}; +} +delete response.User.UserStorage; +session.storage_dirty = false; +} +session.user = response.User; +session.username = session.user.Username; +hide_popup_dialog(); +delete session.hooks.after_error; +update_header(); +if (!tx || !tx._from_recover) $GR.growl('success', "Logged in as: " + session.username); +if (session.nav_after_login) { +Nav.go( session.nav_after_login ); +delete session.nav_after_login; +} +else if (Nav.currentAnchor().match(/^Login/)) { +Nav.go('Home'); +} +else { +Nav.refresh(); +} +Nav.init(); +} +function user_storage_mark() { +Debug.trace("Marking user storage as dirty"); +session.storage_dirty = true; +} +function user_storage_idle() { +if (session.storage_dirty && !session.mouseIsDown) { +user_storage_save(); +session.storage_dirty = false; +} +setTimeout( 'user_storage_idle()', 5000 ); +} +function user_storage_save() { +if (session.user) { +Debug.trace("Committing user storage blob"); +effect_api_send('update_user_storage', { Data: serialize(session.storage) }, 'user_storage_save_finish', { _silent: 1 } ); +} +} +function user_storage_save_finish(response, tx) { +} +function show_default_login_status() { +$('d_sidebar_wrapper_recent_games').hide(); +$('d_login_status').innerHTML = '
' + +'
' + +large_icon_button('key', "Login", '#Home') + '' + spacer(1,1) + '' + +'' + large_icon_button('user_add.png', "Signup", '#CreateAccount') + '
' + +'
'; +$('d_tagline').innerHTML = +'Login' + ' | ' + +'Create Account'; +} +function update_header() { +var html = ''; +html += '
'; +html += ''; +html += ''; +html += ''; +html += ''+spacer(2,2)+''; +html += session.user.FullName + '
'; +html += spacer(1,5) + '
'; +html += 'My Home  |  '; +html += 'Logout'; +html += '
'; +$('d_login_status').innerHTML = html; +$('d_tagline').innerHTML = +'Welcome '+session.user.FirstName+'' + ' | ' + +'My Home' + ' | ' + +'Logout'; +effect_api_get( 'get_user_games', { limit:5, offset:0 }, 'receive_sidebar_recent_games', { } ); +} +function receive_sidebar_recent_games(response, tx) { +var html = ''; +if (response.Rows && response.Rows.Row) { +var games = always_array( response.Rows.Row ); +for (var idx = 0, len = games.length; idx < len; idx++) { +var game = games[idx]; +html += ''; +} +html += ''; +$('d_sidebar_recent_games').innerHTML = html; +$('d_sidebar_wrapper_recent_games').show(); +} +else { +$('d_sidebar_wrapper_recent_games').hide(); +} +} +function check_privilege(key) { +if (!session.user) return false; +if (session.user.Privileges.admin == 1) return true; +if (!key.toString().match(/^\//)) key = '/' + key; +var value = lookup_path(key, session.user.Privileges); +return( value && (value != 0) ); +} +function is_admin() { +return check_privilege('admin'); +} +function upgrade_flash_error() { +return alert("Sorry, file upload requires Adobe Flash Player 9 or higher."); +} +function cancel_user_image_manager() { +upload_destroy(); +hide_popup_dialog(); +delete session.hooks.keys[DELETE_KEY]; +} +function do_user_image_manager(callback) { +if (callback) session.uim_callback = callback; +else session.uim_callback = null; +session.temp_last_user_img = null; +session.temp_last_user_image_filename = ''; +var html = '
'; +html += '
Image Manager
'; +html += '
'; +html += ''; +html += '
'; +html += '
'; +html += ''; +html += ''; +html += ''; +html += ''; +html += ''; +html += '
' + large_icon_button('x', 'Cancel', 'cancel_user_image_manager()') + ' ' + large_icon_button('bullet_upload.png', 'Upload Files...', 'upload_basic()', 'b_upload_user_image') + ' ' + large_icon_button('check', 'Choose', 'do_choose_user_image()', 'btn_choose_user_image', '', 'disabled') + '
'; +html += '
'; +session.hooks.keys[ENTER_KEY] = 'do_choose_user_image'; +session.hooks.keys[ESC_KEY] = 'cancel_user_image_manager'; +session.hooks.keys[DELETE_KEY] = 'do_delete_selected_user_image'; +show_popup_dialog(500, 300, html); +var self = this; +setTimeout( function() { +prep_upload('b_upload_user_image', '/effect/api/upload_user_image', [self, 'do_upload_user_image_2'], ['Image Files', '*.jpg;*.jpe;*.jpeg;*.gif;*.png']); +}, 1 ); +var args = { +limit: 50, +offset: 0, +random: Math.random() +}; +effect_api_get( 'user_images_get', args, 'uim_populate_images', { } ); +} +function do_upload_user_image_2() { +effect_api_mod_touch('user_images_get'); +effect_api_send('user_get', { +Username: session.username +}, [this, 'do_upload_user_image_3']); +} +function do_upload_user_image_3(response) { +if (response.User.LastUploadError) return do_error( "Failed to upload image: " + response.User.LastUploadError ); +do_user_image_manager( session.uim_callback ); +} +function uim_populate_images(response, tx) { +var html = ''; +var base_url = '/effect/api/view/users/' + session.username + '/images'; +if (response.Rows && response.Rows.Row) { +var imgs = always_array( response.Rows.Row ); +for (var idx = 0, len = imgs.length; idx < len; idx++) { +var img = imgs[idx]; +var class_name = ((img.Filename == session.temp_last_user_image_filename) ? 'choose_item_selected' : 'choose_item'); +html += ''; +} +} +else { +html = ''; +} +$('d_user_image_list').innerHTML = html; +} +function do_select_user_image(img, filename) { +if (session.temp_last_user_img) session.temp_last_user_img.className = 'choose_item'; +img.className = 'choose_item_selected'; +$('btn_choose_user_image').removeClass('disabled'); +session.temp_last_user_img = img; +session.temp_last_user_image_filename = filename; +} +function do_delete_selected_user_image() { +if (session.temp_last_user_image_filename) { +effect_api_send('user_image_delete', { Filename: session.temp_last_user_image_filename }, 'do_delete_selected_user_image_finish', {}); +} +} +function do_delete_selected_user_image_finish(response, tx) { +try { $('d_user_image_list').removeChild( session.temp_last_user_img ); } catch(e) {;} +session.temp_last_user_img = null; +session.temp_last_user_image_filename = null; +} +function do_choose_user_image() { +if (!session.temp_last_user_image_filename) return; +if (session.uim_callback) { +fire_callback( session.uim_callback, session.temp_last_user_image_filename ); +} +cancel_user_image_manager(); +} +function user_image_thumbnail(filename, width, height, attribs) { +var username = session.username; +if (filename.match(/^(\w+)\/(.+)$/)) { +username = RegExp.$1; +filename = RegExp.$2; +} +var url = '/effect/api/view/users/' + username + '/images/' + filename.replace(/\.(\w+)$/, '_thumb.jpg'); +return ''; +} +function get_user_display(username, full_name, base_url) { +if (!base_url) base_url = ''; +return icon('user', full_name || username, base_url + '#User/' + username); +} +function get_game_tab_bar(game_id, cur_page_name) { +return tab_bar([ +['#Game/' + game_id, 'Game', 'controller.png'], +['#GameDisplay/' + game_id, 'Display', 'monitor.png'], +['#GameAssets/' + game_id, 'Assets', 'folder_page_white.png'], +['#GameObjects/' + game_id, 'Objects', 'bricks.png'], +['#GameAudio/' + game_id, 'Audio', 'sound.gif'], +['#GameKeys/' + game_id, 'Keyboard', 'keyboard.png'], +['#GameLevels/' + game_id, 'Levels', 'world.png'], +['#GamePublisher/' + game_id, 'Publish', 'cd.png'] +], cur_page_name); +} +function get_user_tab_bar(cur_page_name) { +var tabs = [ +['#Home', 'My Home', 'house.png'] +]; +tabs.push( ['#MyAccount', 'Edit Account', 'user_edit.png'] ); +tabs.push( ['#ArticleEdit', 'Post Article', 'page_white_edit.png'] ); +if (config.ProEnabled) { +tabs.push( ['#UserPayments', 'Payments', 'money.png'] ); +} +tabs.push( ['#UserLog', 'Security Log', 'application_view_detail.png'] ); +return tab_bar(tabs, cur_page_name); +} +function get_admin_tab_bar(cur_page_name) { +var tabs = []; +tabs.push( ['#Admin', 'Admin', 'lock.png'] ); +tabs.push( ['#TicketSearch/bugs', 'Bug Tracker', 'bug.png'] ); +tabs.push( ['#TicketSearch/helpdesk', 'Help Desk', 'telephone.png'] ); +tabs.push( ['#AdminReport', 'Reports', 'chart_pie.png'] ); +return tab_bar(tabs, cur_page_name); +} +function get_string(path, args) { +assert(window.config, "get_string() called before config loaded"); +if (!args) args = {}; +args.config = config; +args.session = session; +args.query = session.query; +var value = lookup_path(path, config.Strings); +return (typeof(value) == 'string') ? substitute(value, args) : value; +} +function normalize_dir_path(path) { +if (!path.match(/^\//)) path = '/' + path; +if (!path.match(/\/$/)) path += '/'; +return path; +} +function textedit_window_save(storage_key, filename, content, callback) { +if (!callback) callback = null; +effect_api_mod_touch('textedit'); +if (storage_key.match(/^\/games\/([a-z0-9][a-z0-9\-]*[a-z0-9])\/assets(.+)$/)) { +var game_id = RegExp.$1; +var path = RegExp.$2; +show_progress_dialog(1, "Saving file..."); +effect_api_send('asset_save_file_contents', { +GameID: game_id, +Path: path, +Filename: filename, +Content: content +}, 'textedit_window_save_finish', { _mode: 'asset', _game_id: game_id, _filename: filename, _callback: callback } ); +} +else { +show_progress_dialog(1, "Saving data..."); +effect_api_send('admin_save_file_contents', { +Path: storage_key, +Filename: filename, +Content: content +}, 'textedit_window_save_finish', { _mode: 'admin', _storage_key: storage_key, _filename: filename, _callback: callback } ); +} +} +function textedit_window_save_finish(response, tx) { +hide_progress_dialog(); +if (tx._mode == 'asset') { +do_message('success', "Saved asset: \""+tx._filename+"\""); +show_glog_widget(); +} +else { +do_message('success', "Saved data: \""+tx._storage_key+'/'+tx._filename+"\""); +} +if (tx._callback) tx._callback(); +} +function do_buy(args) { +$P().hide(); +$('d_page_loading').show(); +effect_api_send('create_order', args, 'do_buy_redirect', { _buy_args: args } ); +} +function do_buy_redirect(response, tx) { +var args = tx._buy_args; +$('fe_gco_title').value = args.Title || ''; +$('fe_gco_desc').value = args.Desc || ''; +$('fe_gco_price').value = args.Price || ''; +$('fe_gco_after').value = args.After || ''; +$('fe_gco_unique_id').value = response.OrderID; +Debug.trace('payment', "Redirecting to Google Checkout"); +setTimeout( function() { $('BB_BuyButtonForm').submit(); }, 1 ); +} +function show_glog_widget(game_id) { +if (!game_id) game_id = session.glog_game_id; +if (!game_id) { +$('glog_widget').hide(); +return; +} +if (game_id != session.glog_game_id) { +$('glog_widget').hide(); +session.glog_game_id = game_id; +update_glog_widget(game_id); +} +else { +$('glog_widget').show(); +setTimeout( function() { update_glog_widget(game_id); }, 500 ); +} +} +function update_glog_widget(game_id) { +effect_api_get('game_get_log', { +id: game_id, +offset: 0, +limit: 1, +rand: Math.random() +}, 'receive_glog_data', { _game_id: game_id }); +} +function receive_glog_data(response, tx) { +var game_id = tx._game_id; +if (response && response.Rows && response.Rows.Row) { +var rows = always_array( response.Rows.Row ); +var row = rows[0]; +var html = ''; +html += '
'; +html += '
Latest Game Activity
'; +html += ''; +html += ''; +html += '
'; +html += '
'; +html += ''; +html += ''; +html += ''; +html += '
' + get_buddy_icon_display(row.Username, 1, 0) + ''; +html += '
' + icon( get_icon_for_glog_type(row.Type), ''+row.Message+'' ) + '
'; +html += '
' + get_relative_date(row.Date, true) + '
'; +html += '
'; +$('glog_widget').innerHTML = html; +$('glog_widget').show(); +} +} +function show_glog_post_dialog(game_id) { +hide_popup_dialog(); +delete session.progress; +var html = ''; +html += '
'; +html += '"; + second_cell = ""; + row = $("").attr("id", "s" + index).attr("class", "location_row").html(first_cell + second_cell); + $locationsDiv.append(row); + } + if (index === this.numSearchToDisplay) { + $locationsDiv.append(""); + return $locationsDiv.append(""); + } + }, this); + return this.geocoder.geocode({ + address: address + }, __bind(function(result, status) { + if (status !== "OK") { + $('.error_message').html(t("Search Address Failed")).fadeIn(); + return; + } + _.each(result, showResults); + $("#search_results").html($locationsDiv); + this.locationChange("search"); + this.searchResults = result; + return this.displaySearchLoc(); + }, this)); + }; + ClientsRequestView.prototype.mouseoverLocation = function(e) { + var $el, id, marker; + $el = $(e.currentTarget); + id = $el.attr("id").substring(1); + marker = this.markers[id]; + return marker.setAnimation(google.maps.Animation.BOUNCE); + }; + ClientsRequestView.prototype.mouseoutLocation = function(e) { + var $el, id, marker; + $el = $(e.currentTarget); + id = $el.attr("id").substring(1); + marker = this.markers[id]; + return marker.setAnimation(null); + }; + ClientsRequestView.prototype.searchLocation = function(e) { + e.preventDefault(); + $("#address").val($(e.currentTarget).html()); + return this.searchAddress(); + }; + ClientsRequestView.prototype.favoriteClick = function(e) { + var index, location; + e.preventDefault(); + $(".favorites").attr("href", ""); + index = $(e.currentTarget).removeAttr("href").attr("id"); + location = new google.maps.LatLng(USER.locations[index].latitude, USER.locations[index].longitude); + return this.panToLocation(location); + }; + ClientsRequestView.prototype.clickLocation = function(e) { + var id; + id = $(e.currentTarget).attr("id").substring(1); + return this.panToLocation(this.markers[id].getPosition()); + }; + ClientsRequestView.prototype.panToLocation = function(location) { + this.map.panTo(location); + this.map.setZoom(16); + return this.pickup_icon.setPosition(location); + }; + ClientsRequestView.prototype.locationLinkHandle = function(e) { + var panelName; + e.preventDefault(); + panelName = $(e.currentTarget).attr("id"); + return this.locationChange(panelName); + }; + ClientsRequestView.prototype.locationChange = function(type) { + $(".locations_link").attr("href", "").css("font-weight", "normal"); + switch (type) { + case "favorite": + $(".search_results").attr("href", ""); + $(".locations_link#favorite").removeAttr("href").css("font-weight", "bold"); + $("#search_results").hide(); + $("#favorite_results").fadeIn(); + return this.displayFavLoc(); + case "search": + $(".favorites").attr("href", ""); + $(".locations_link#search").removeAttr("href").css("font-weight", "bold"); + $("#favorite_results").hide(); + $("#search_results").fadeIn(); + return this.displaySearchLoc(); + } + }; + ClientsRequestView.prototype.rateTrip = function(e) { + var rating; + rating = $(e.currentTarget).attr("id"); + $(".stars").attr("src", "/web/img/star_inactive.png"); + return _(rating).times(function(index) { + return $(".stars#" + (index + 1)).attr("src", "/web/img/star_active.png"); + }); + }; + ClientsRequestView.prototype.pickupHandle = function(e) { + var $el, callback, message; + e.preventDefault(); + $el = $(e.currentTarget).find("span"); + switch ($el.html()) { + case t("Request Pickup"): + _.delay(this.requestRide, 3000); + $("#status_message").html(t("Sending pickup request...")); + $el.html(t("Cancel Pickup")).parent().attr("class", "button_red"); + this.pickup_icon.setDraggable(false); + this.map.panTo(this.pickup_icon.getPosition()); + return this.map.setZoom(18); + case t("Cancel Pickup"): + if (this.status === "ready") { + $el.html(t("Request Pickup")).parent().attr("class", "button_green"); + return this.pickup_icon.setDraggable(true); + } else { + callback = __bind(function(v, m, f) { + if (v) { + this.AskDispatch("PickupCanceledClient"); + return this.setStatus("ready"); + } + }, this); + message = t("Cancel Request Prompt"); + if (this.status === "arriving") { + message = 'Cancel Request Arrived Prompt'; + } + return $.prompt(message, { + buttons: { + Ok: true, + Cancel: false + }, + callback: callback + }); + } + } + }; + ClientsRequestView.prototype.requestRide = function() { + if ($("#pickupHandle").find("span").html() === t("Cancel Pickup")) { + this.AskDispatch("Pickup"); + return this.setStatus("searching"); + } + }; + ClientsRequestView.prototype.removeCabs = function() { + _.each(this.cabs, __bind(function(point) { + return point.setMap(null); + }, this)); + return this.cabs = []; + }; + ClientsRequestView.prototype.addToFavLoc = function(e) { + var $el, lat, lng, nickname; + e.preventDefault(); + $el = $(e.currentTarget); + $el.find(".error_message").html(""); + nickname = $el.find("#favLocNickname").val().toString(); + lat = $el.find("#pickupLat").val().toString(); + lng = $el.find("#pickupLng").val().toString(); + if (nickname.length < 3) { + $el.find(".error_message").html(t("Favorite Location Nickname Length Error")); + return; + } + this.ShowSpinner("submit"); + return $.ajax({ + type: 'POST', + url: API + "/locations", + dataType: 'json', + data: { + token: USER.token, + nickname: nickname, + latitude: lat, + longitude: lng + }, + success: __bind(function(data, textStatus, jqXHR) { + return $el.html(t("Favorite Location Save Succeeded")); + }, this), + error: __bind(function(jqXHR, textStatus, errorThrown) { + return $el.find(".error_message").html(t("Favorite Location Save Failed")); + }, this), + complete: __bind(function(data) { + return this.HideSpinner(); + }, this) + }); + }; + ClientsRequestView.prototype.showFavLoc = function(e) { + $(e.currentTarget).fadeOut(); + return $("#favLoc_form").fadeIn(); + }; + ClientsRequestView.prototype.selectInputText = function(e) { + e.currentTarget.focus(); + return e.currentTarget.select(); + }; + ClientsRequestView.prototype.displayFavLoc = function() { + var alphabet, bounds; + alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + this.removeMarkers(); + bounds = new google.maps.LatLngBounds(); + _.each(USER.locations, __bind(function(location, index) { + var marker; + marker = new google.maps.Marker({ + position: new google.maps.LatLng(location.latitude, location.longitude), + map: this.map, + title: t("Favorite Location Title", { + id: alphabet != null ? alphabet[index] : void 0 + }), + icon: "https://www.google.com/mapfiles/marker" + alphabet[index] + ".png" + }); + this.markers.push(marker); + bounds.extend(marker.getPosition()); + return google.maps.event.addListener(marker, 'click', __bind(function() { + return this.pickup_icon.setPosition(marker.getPosition()); + }, this)); + }, this)); + this.pickup_icon.setPosition(_.first(this.markers).getPosition()); + return this.map.fitBounds(bounds); + }; + ClientsRequestView.prototype.displaySearchLoc = function() { + var alphabet; + alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + this.removeMarkers(); + return _.each(this.searchResults, __bind(function(result, index) { + var marker; + if (index < this.numSearchToDisplay) { + marker = new google.maps.Marker({ + position: result.geometry.location, + map: this.map, + title: t("Search Location Title", { + id: alphabet != null ? alphabet[index] : void 0 + }), + icon: "https://www.google.com/mapfiles/marker" + alphabet[index] + ".png" + }); + this.markers.push(marker); + return this.panToLocation(result.geometry.location); + } + }, this)); + }; + ClientsRequestView.prototype.removeMarkers = function() { + _.each(this.markers, __bind(function(marker) { + return marker.setMap(null); + }, this)); + return this.markers = []; + }; + ClientsRequestView.prototype.AskDispatch = function(ask, options) { + var attrs, lowestETA, processData, showCab; + if (ask == null) { + ask = ""; + } + if (options == null) { + options = {}; + } + switch (ask) { + case "NearestCab": + attrs = { + latitude: this.pickup_icon.getPosition().lat(), + longitude: this.pickup_icon.getPosition().lng() + }; + lowestETA = 99999; + showCab = __bind(function(cab) { + var point; + point = new google.maps.Marker({ + position: new google.maps.LatLng(cab.latitude, cab.longitude), + map: this.map, + icon: this.cabMarker, + title: t("ETA Message", { + minutes: app.helpers.FormatSeconds(cab != null ? cab.eta : void 0, true) + }) + }); + if (cab.eta < lowestETA) { + lowestETA = cab.eta; + } + return this.cabs.push(point); + }, this); + processData = __bind(function(data, textStatus, jqXHR) { + if (this.status === "ready") { + this.removeCabs(); + if (data.sorry) { + $("#status_message").html(data.sorry).fadeIn(); + } else { + _.each(data.driverLocations, showCab); + $("#status_message").html(t("Nearest Cab Message", { + minutes: app.helpers.FormatSeconds(lowestETA, true) + })).fadeIn(); + } + if (Backbone.history.fragment === "!/request") { + return _.delay(this.showCabs, this.pollInterval); + } + } + }, this); + return this.AjaxCall(ask, processData, attrs); + case "StatusClient": + processData = __bind(function(data, textStatus, jqXHR) { + var bounds, cabLocation, locationSaved, point, userLocation; + if (data.messageType === "OK") { + switch (data.status) { + case "completed": + this.removeCabs(); + this.setStatus("rate"); + return this.fetchTripDetails(data.tripID); + case "open": + return this.setStatus("ready"); + case "begintrip": + this.setStatus("riding"); + cabLocation = new google.maps.LatLng(data.latitude, data.longitude); + this.removeCabs(); + this.pickup_icon.setMap(null); + point = new google.maps.Marker({ + position: cabLocation, + map: this.map, + icon: this.cabMarker + }); + this.cabs.push(point); + this.map.panTo(point.getPosition()); + $("#rideName").html(data.driverName); + $("#ridePhone").html(data.driverMobile); + $("#ride_address_wrapper").hide(); + if (Backbone.history.fragment === "!/request") { + return _.delay(this.AskDispatch, this.pollInterval, "StatusClient"); + } + break; + case "pending": + this.setStatus("searching"); + if (Backbone.history.fragment === "!/request") { + return _.delay(this.AskDispatch, this.pollInterval, "StatusClient"); + } + break; + case "accepted": + case "arrived": + if (data.status === "accepted") { + this.setStatus("waiting"); + $("#status_message").html(t("Arrival ETA Message", { + minutes: app.helpers.FormatSeconds(data.eta, true) + })); + } else { + this.setStatus("arriving"); + $("#status_message").html(t("Arriving Now Message")); + } + userLocation = new google.maps.LatLng(data.pickupLocation.latitude, data.pickupLocation.longitude); + cabLocation = new google.maps.LatLng(data.latitude, data.longitude); + this.pickup_icon.setPosition(userLocation); + this.removeCabs(); + $("#rideName").html(data.driverName); + $("#ridePhone").html(data.driverMobile); + if ($("#rideAddress").html() === "") { + locationSaved = false; + _.each(USER.locations, __bind(function(location) { + if (parseFloat(location.latitude) === parseFloat(data.pickupLocation.latitude) && parseFloat(location.longitude) === parseFloat(data.pickupLocation.longitude)) { + return locationSaved = true; + } + }, this)); + if (locationSaved) { + $("#addToFavButton").hide(); + } + $("#pickupLat").val(data.pickupLocation.latitude); + $("#pickupLng").val(data.pickupLocation.longitude); + this.geocoder.geocode({ + location: userLocation + }, __bind(function(result, status) { + $("#rideAddress").html(result[0].formatted_address); + return $("#favLocNickname").val("" + result[0].address_components[0].short_name + " " + result[0].address_components[1].short_name); + }, this)); + } + point = new google.maps.Marker({ + position: cabLocation, + map: this.map, + icon: this.cabMarker + }); + this.cabs.push(point); + bounds = bounds = new google.maps.LatLngBounds(); + bounds.extend(cabLocation); + bounds.extend(userLocation); + this.map.fitBounds(bounds); + if (Backbone.history.fragment === "!/request") { + return _.delay(this.AskDispatch, this.pollInterval, "StatusClient"); + } + } + } + }, this); + return this.AjaxCall(ask, processData); + case "Pickup": + attrs = { + latitude: this.pickup_icon.getPosition().lat(), + longitude: this.pickup_icon.getPosition().lng() + }; + processData = __bind(function(data, textStatus, jqXHR) { + if (data.messageType === "Error") { + return $("#status_message").html(data.description); + } else { + return this.AskDispatch("StatusClient"); + } + }, this); + return this.AjaxCall(ask, processData, attrs); + case "PickupCanceledClient": + processData = __bind(function(data, textStatus, jqXHR) { + if (data.messageType === "OK") { + return this.setStatus("ready"); + } else { + return $("#status_message").html(data.description); + } + }, this); + return this.AjaxCall(ask, processData, attrs); + case "RatingDriver": + attrs = { + rating: options.rating + }; + processData = __bind(function(data, textStatus, jqXHR) { + if (data.messageType === "OK") { + this.setStatus("init"); + } else { + $("status_message").html(t("Rating Driver Failed")); + } + return this.HideSpinner(); + }, this); + return this.AjaxCall(ask, processData, attrs); + case "Feedback": + attrs = { + message: options.message + }; + processData = __bind(function(data, textStatus, jqXHR) { + if (data.messageType === "OK") { + return alert("rated"); + } + }, this); + return this.AjaxCall(ask, processData, attrs); + } + }; + ClientsRequestView.prototype.AjaxCall = function(type, successCallback, attrs) { + if (attrs == null) { + attrs = {}; + } + _.extend(attrs, { + token: USER.token, + messageType: type, + app: "client", + version: "1.0.60", + device: "web" + }); + return $.ajax({ + type: 'POST', + url: DISPATCH + "/", + processData: false, + data: JSON.stringify(attrs), + success: successCallback, + dataType: 'json', + error: __bind(function(jqXHR, textStatus, errorThrown) { + $("#status_message").html(errorThrown); + return this.HideSpinner(); + }, this) + }); + }; + return ClientsRequestView; + })(); +}).call(this); +}, "views/clients/settings": function(exports, require, module) {(function() { + var clientsSettingsTemplate; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + clientsSettingsTemplate = require('templates/clients/settings'); + exports.ClientsSettingsView = (function() { + __extends(ClientsSettingsView, UberView); + function ClientsSettingsView() { + this.render = __bind(this.render, this); + this.initialize = __bind(this.initialize, this); + ClientsSettingsView.__super__.constructor.apply(this, arguments); + } + ClientsSettingsView.prototype.id = 'settings_view'; + ClientsSettingsView.prototype.className = 'view_container'; + ClientsSettingsView.prototype.events = { + 'submit #profile_pic_form': 'processPicUpload', + 'click #submit_pic': 'processPicUpload', + 'click a.setting_change': "changeTab", + 'submit #edit_info_form': "submitInfo", + 'click #change_password': 'changePass' + }; + ClientsSettingsView.prototype.divs = { + 'info_div': "Information", + 'pic_div': "Picture" + }; + ClientsSettingsView.prototype.pageTitle = t("Settings") + " | " + t("Uber"); + ClientsSettingsView.prototype.tabTitle = { + 'info_div': t("Information"), + 'pic_div': t("Picture") + }; + ClientsSettingsView.prototype.initialize = function() { + return this.mixin(require('web-lib/mixins/i18n_phone_form').i18nPhoneForm); + }; + ClientsSettingsView.prototype.render = function(type) { + if (type == null) { + type = "info"; + } + this.RefreshUserInfo(__bind(function() { + var $el, alphabet; + this.delegateEvents(); + this.HideSpinner(); + alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + $el = $(this.el); + $(this.el).html(clientsSettingsTemplate({ + type: type + })); + $el.find("#" + type + "_div").show(); + $el.find("a[href='" + type + "_div']").parent().addClass("active"); + return document.title = "" + this.tabTitle[type + '_div'] + " " + this.pageTitle; + }, this)); + this.delegateEvents(); + return this; + }; + ClientsSettingsView.prototype.changeTab = function(e) { + var $eTarget, $el, div, link, pageDiv, _i, _j, _len, _len2, _ref, _ref2; + e.preventDefault(); + $eTarget = $(e.currentTarget); + this.ClearGlobalStatus(); + $el = $(this.el); + _ref = $el.find(".setting_change"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + link = _ref[_i]; + $(link).parent().removeClass("active"); + } + $eTarget.parent().addClass("active"); + _ref2 = _.keys(this.divs); + for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { + div = _ref2[_j]; + $el.find("#" + div).hide(); + } + pageDiv = $eTarget.attr('href'); + $el.find("#" + pageDiv).show(); + Backbone.history.navigate("!/settings/" + (this.divs[pageDiv].toLowerCase().replace(" ", "-")), false); + document.title = "" + this.tabTitle[pageDiv] + " " + this.pageTitle; + if (pageDiv === "loc_div") { + try { + google.maps.event.trigger(this.map, 'resize'); + return this.map.fitBounds(this.bounds); + } catch (_e) {} + } + }; + ClientsSettingsView.prototype.submitInfo = function(e) { + var $e, attrs, client, options; + $('#global_status').find('.success_message').text(''); + $('#global_status').find('.error_message').text(''); + $('.error_message').text(''); + e.preventDefault(); + $e = $(e.currentTarget); + attrs = $e.serializeToJson(); + attrs['mobile_country_id'] = this.$('#mobile_country_id').val(); + if (attrs['password'] === '') { + delete attrs['password']; + } + options = { + success: __bind(function(response) { + this.ShowSuccess(t("Information Update Succeeded")); + return this.RefreshUserInfo(); + }, this), + error: __bind(function(model, data) { + var errors; + if (data.status === 406) { + errors = JSON.parse(data.responseText); + return _.each(_.keys(errors), function(field) { + return $("#" + field).parent().find('span.error_message').text(errors[field]); + }); + } else { + return this.ShowError(t("Information Update Failed")); + } + }, this), + type: "PUT" + }; + client = new app.models.client({ + id: USER.id + }); + return client.save(attrs, options); + }; + ClientsSettingsView.prototype.changePass = function(e) { + e.preventDefault(); + $(e.currentTarget).hide(); + return $("#password").show(); + }; + ClientsSettingsView.prototype.processPicUpload = function(e) { + e.preventDefault(); + this.ShowSpinner("submit"); + return $.ajaxFileUpload({ + url: API + '/user_pictures', + secureuri: false, + fileElementId: 'picture', + data: { + token: USER.token + }, + dataType: 'json', + complete: __bind(function(data, status) { + this.HideSpinner(); + if (status === 'success') { + this.ShowSuccess(t("Picture Update Succeeded")); + return this.RefreshUserInfo(__bind(function() { + return $("#settingsProfPic").attr("src", USER.picture_url + ("?" + (Math.floor(Math.random() * 1000)))); + }, this)); + } else { + if (data.error) { + return this.ShowError(data.error); + } else { + return this.ShowError("Picture Update Failed"); + } + } + }, this) + }); + }; + return ClientsSettingsView; + })(); +}).call(this); +}, "views/clients/sign_up": function(exports, require, module) {(function() { + var clientsSignUpTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + clientsSignUpTemplate = require('templates/clients/sign_up'); + exports.ClientsSignUpView = (function() { + __extends(ClientsSignUpView, UberView); + function ClientsSignUpView() { + ClientsSignUpView.__super__.constructor.apply(this, arguments); + } + ClientsSignUpView.prototype.id = 'signup_view'; + ClientsSignUpView.prototype.className = 'view_container'; + ClientsSignUpView.prototype.initialize = function() { + this.mixin(require('web-lib/mixins/i18n_phone_form').i18nPhoneForm); + return $('#location_country').live('change', function() { + if (!$('#mobile').val()) { + return $('#mobile_country').find("option[value=" + ($(this).val()) + "]").attr('selected', 'selected').end().trigger('change'); + } + }); + }; + ClientsSignUpView.prototype.events = { + 'submit form': 'signup', + 'click button': 'signup', + 'change #card_number': 'showCardType', + 'change #location_country': 'countryChange' + }; + ClientsSignUpView.prototype.render = function(invite) { + this.HideSpinner(); + $(this.el).html(clientsSignUpTemplate({ + invite: invite + })); + return this; + }; + ClientsSignUpView.prototype.signup = function(e) { + var $el, attrs, client, error_messages, options; + e.preventDefault(); + $el = $("form"); + $el.find('#terms_error').hide(); + if (!$el.find('#signup_terms input[type=checkbox]').attr('checked')) { + $('#spinner.submit').hide(); + $el.find('#terms_error').show(); + return; + } + error_messages = $el.find('.error_message').html(""); + attrs = { + first_name: $el.find('#first_name').val(), + last_name: $el.find('#last_name').val(), + email: $el.find('#email').val(), + password: $el.find('#password').val(), + location_country: $el.find('#location_country option:selected').attr('data-iso2'), + location: $el.find('#location').val(), + language: $el.find('#language').val(), + mobile_country: $el.find('#mobile_country option:selected').attr('data-iso2'), + mobile: $el.find('#mobile').val(), + card_number: $el.find('#card_number').val(), + card_expiration_month: $el.find('#card_expiration_month').val(), + card_expiration_year: $el.find('#card_expiration_year').val(), + card_code: $el.find('#card_code').val(), + use_case: $el.find('#use_case').val(), + promotion_code: $el.find('#promotion_code').val() + }; + options = { + statusCode: { + 200: function(response) { + $.cookie('token', response.token); + amplify.store('USERjson', response); + app.refreshMenu(); + return app.routers.clients.navigate('!/dashboard', true); + }, + 406: function(e) { + var error, errors, _i, _len, _ref, _results; + errors = JSON.parse(e.responseText); + _ref = _.keys(errors); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + error = _ref[_i]; + _results.push($('#' + error).parent().find('span').html($('#' + error).parent().find('span').html() + " " + errors[error])); + } + return _results; + } + }, + complete: __bind(function(response) { + return this.HideSpinner(); + }, this) + }; + client = new app.models.client; + $('.spinner#submit').show(); + return client.save(attrs, options); + }; + ClientsSignUpView.prototype.countryChange = function(e) { + var $e; + $e = $(e.currentTarget); + return $("#mobile_country").val($e.val()).trigger('change'); + }; + ClientsSignUpView.prototype.showCardType = function(e) { + var $el, reAmerica, reDiscover, reMaster, reVisa, validCard; + reVisa = /^4\d{3}-?\d{4}-?\d{4}-?\d{4}$/; + reMaster = /^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/; + reAmerica = /^6011-?\d{4}-?\d{4}-?\d{4}$/; + reDiscover = /^3[4,7]\d{13}$/; + $el = $("#card_logos_signup"); + validCard = false; + if (e.currentTarget.value.match(reVisa)) { + $el.find("#overlay_left").css('width', "0px"); + return $el.find("#overlay_right").css('width', "75%"); + } else if (e.currentTarget.value.match(reMaster)) { + $el.find("#overlay_left").css('width', "25%"); + return $el.find("#overlay_right").css('width', "50%"); + } else if (e.currentTarget.value.match(reAmerica)) { + $el.find("#overlay_left").css('width', "75%"); + $el.find("#overlay_right").css('width', "0px"); + return console.log("amex"); + } else if (e.currentTarget.value.match(reDiscover)) { + $el.find("#overlay_left").css('width', "50%"); + return $el.find("#overlay_right").css('width', "25%"); + } else { + $el.find("#overlay_left").css('width', "0px"); + return $el.find("#overlay_right").css('width', "0px"); + } + }; + return ClientsSignUpView; + })(); +}).call(this); +}, "views/clients/trip_detail": function(exports, require, module) {(function() { + var clientsTripDetailTemplate; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + clientsTripDetailTemplate = require('templates/clients/trip_detail'); + exports.TripDetailView = (function() { + __extends(TripDetailView, UberView); + function TripDetailView() { + this.resendReceipt = __bind(this.resendReceipt, this); + TripDetailView.__super__.constructor.apply(this, arguments); + } + TripDetailView.prototype.id = 'trip_detail_view'; + TripDetailView.prototype.className = 'view_container'; + TripDetailView.prototype.events = { + 'click a#fare_review': 'showFareReview', + 'click #fare_review_hide': 'hideFareReview', + 'submit #form_review_form': 'submitFareReview', + 'click #submit_fare_review': 'submitFareReview', + 'click .resendReceipt': 'resendReceipt' + }; + TripDetailView.prototype.render = function(id) { + if (id == null) { + id = 'invalid'; + } + this.ReadUserInfo(); + this.HideSpinner(); + this.model = new app.models.trip({ + id: id + }); + this.model.fetch({ + data: { + relationships: 'points,driver,city.country' + }, + dataType: 'json', + success: __bind(function() { + var trip; + trip = this.model; + $(this.el).html(clientsTripDetailTemplate({ + trip: trip + })); + this.RequireMaps(__bind(function() { + var bounds, endPos, map, myOptions, path, polyline, startPos; + bounds = new google.maps.LatLngBounds(); + path = []; + _.each(this.model.get('points'), __bind(function(point) { + path.push(new google.maps.LatLng(point.lat, point.lng)); + return bounds.extend(_.last(path)); + }, this)); + myOptions = { + zoom: 12, + center: path[0], + mapTypeId: google.maps.MapTypeId.ROADMAP, + zoomControl: false, + rotateControl: false, + panControl: false, + mapTypeControl: false, + scrollwheel: false + }; + map = new google.maps.Map(document.getElementById("trip_details_map"), myOptions); + map.fitBounds(bounds); + startPos = new google.maps.Marker({ + position: _.first(path), + map: map, + title: t("Trip started here"), + icon: 'https://uber-static.s3.amazonaws.com/marker_start.png' + }); + endPos = new google.maps.Marker({ + position: _.last(path), + map: map, + title: t("Trip ended here"), + icon: 'https://uber-static.s3.amazonaws.com/marker_end.png' + }); + startPos.setMap(map); + endPos.setMap(map); + polyline = new google.maps.Polyline({ + path: path, + strokeColor: '#003F87', + strokeOpacity: 1, + strokeWeight: 5 + }); + return polyline.setMap(map); + }, this)); + return this.HideSpinner(); + }, this) + }); + this.ShowSpinner('load'); + this.delegateEvents(); + return this; + }; + TripDetailView.prototype.showFareReview = function(e) { + e.preventDefault(); + $('#fare_review_box').slideDown(); + return $('#fare_review').hide(); + }; + TripDetailView.prototype.hideFareReview = function(e) { + e.preventDefault(); + $('#fare_review_box').slideUp(); + return $('#fare_review').show(); + }; + TripDetailView.prototype.submitFareReview = function(e) { + var attrs, errorMessage, id, options; + e.preventDefault(); + errorMessage = $(".error_message"); + errorMessage.hide(); + id = $("#tripid").val(); + this.model = new app.models.trip({ + id: id + }); + attrs = { + note: $('#form_review_message').val(), + note_type: 'client_fare_review' + }; + options = { + success: __bind(function(response) { + $(".success_message").fadeIn(); + return $("#fare_review_form_wrapper").slideUp(); + }, this), + error: __bind(function(error) { + return errorMessage.fadeIn(); + }, this) + }; + return this.model.save(attrs, options); + }; + TripDetailView.prototype.resendReceipt = function(e) { + var $e; + e.preventDefault(); + $e = $(e.currentTarget); + this.$(".resendReceiptSuccess").empty().show(); + this.$(".resentReceiptError").empty().show(); + e.preventDefault(); + $('#spinner').show(); + return $.ajax('/api/trips/func/resend_receipt', { + data: { + token: $.cookie('token'), + trip_id: this.model.id + }, + type: 'POST', + complete: __bind(function(xhr) { + var response; + response = JSON.parse(xhr.responseText); + $('#spinner').hide(); + switch (xhr.status) { + case 200: + this.$(".resendReceiptSuccess").html("Receipt has been emailed"); + return this.$(".resendReceiptSuccess").fadeOut(2000); + default: + this.$(".resendReceiptError").html("Receipt has failed to be emailed"); + return this.$(".resendReceiptError").fadeOut(2000); + } + }, this) + }); + }; + return TripDetailView; + })(); +}).call(this); +}, "views/shared/menu": function(exports, require, module) {(function() { + var menuTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + menuTemplate = require('templates/shared/menu'); + exports.SharedMenuView = (function() { + __extends(SharedMenuView, Backbone.View); + function SharedMenuView() { + SharedMenuView.__super__.constructor.apply(this, arguments); + } + SharedMenuView.prototype.id = 'menu_view'; + SharedMenuView.prototype.render = function() { + var type; + if ($.cookie('token') === null) { + type = 'guest'; + } else { + type = 'client'; + } + $(this.el).html(menuTemplate({ + type: type + })); + return this; + }; + return SharedMenuView; + })(); +}).call(this); +}, "web-lib/collections/countries": function(exports, require, module) {(function() { + var UberCollection; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + UberCollection = require('web-lib/uber_collection').UberCollection; + exports.CountriesCollection = (function() { + __extends(CountriesCollection, UberCollection); + function CountriesCollection() { + CountriesCollection.__super__.constructor.apply(this, arguments); + } + CountriesCollection.prototype.model = app.models.country; + CountriesCollection.prototype.url = '/countries'; + return CountriesCollection; + })(); +}).call(this); +}, "web-lib/collections/vehicle_types": function(exports, require, module) {(function() { + var UberCollection, vehicleType, _ref; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + UberCollection = require('web-lib/uber_collection').UberCollection; + vehicleType = (typeof app !== "undefined" && app !== null ? (_ref = app.models) != null ? _ref.vehicleType : void 0 : void 0) || require('models/vehicle_type').VehicleType; + exports.VehicleTypesCollection = (function() { + __extends(VehicleTypesCollection, UberCollection); + function VehicleTypesCollection() { + VehicleTypesCollection.__super__.constructor.apply(this, arguments); + } + VehicleTypesCollection.prototype.model = vehicleType; + VehicleTypesCollection.prototype.url = '/vehicle_types'; + VehicleTypesCollection.prototype.defaultColumns = ['id', 'created_at', 'updated_at', 'deleted_at', 'created_by_user_id', 'updated_by_user_id', 'city_id', 'type', 'make', 'model', 'capacity', 'minimum_year', 'actions']; + VehicleTypesCollection.prototype.tableColumns = function(cols) { + var actions, c, capacity, city_id, columnValues, created_at, created_by_user_id, deleted_at, headerRow, id, make, minimum_year, model, type, updated_at, updated_by_user_id, _i, _len; + id = { + sTitle: 'Id' + }; + created_at = { + sTitle: 'Created At (UTC)', + 'sType': 'string' + }; + updated_at = { + sTitle: 'Updated At (UTC)', + 'sType': 'string' + }; + deleted_at = { + sTitle: 'Deleted At (UTC)', + 'sType': 'string' + }; + created_by_user_id = { + sTitle: 'Created By' + }; + updated_by_user_id = { + sTitle: 'Updated By' + }; + city_id = { + sTitle: 'City' + }; + type = { + sTitle: 'Type' + }; + make = { + sTitle: 'Make' + }; + model = { + sTitle: 'Model' + }; + capacity = { + sTitle: 'Capacity' + }; + minimum_year = { + sTitle: 'Min. Year' + }; + actions = { + sTitle: 'Actions' + }; + columnValues = { + id: id, + created_at: created_at, + updated_at: updated_at, + deleted_at: deleted_at, + created_by_user_id: created_by_user_id, + updated_by_user_id: updated_by_user_id, + city_id: city_id, + type: type, + make: make, + model: model, + capacity: capacity, + minimum_year: minimum_year, + actions: actions + }; + headerRow = []; + for (_i = 0, _len = cols.length; _i < _len; _i++) { + c = cols[_i]; + if (columnValues[c]) { + headerRow.push(columnValues[c]); + } + } + return headerRow; + }; + return VehicleTypesCollection; + })(); +}).call(this); +}, "web-lib/helpers": function(exports, require, module) {(function() { + var __indexOf = Array.prototype.indexOf || function(item) { + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === item) return i; + } + return -1; + }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + exports.helpers = { + pin: function(num, color) { + if (color == null) { + color = 'FF0000'; + } + return ""; + }, + reverseGeocode: function(latitude, longitude) { + if (latitude && longitude) { + return "" + latitude + ", " + longitude + ""; + } else { + return ''; + } + }, + linkedName: function(model) { + var first_name, id, last_name, role, url; + role = model.role || model.get('role'); + id = model.id || model.get('id'); + first_name = model.first_name || model.get('first_name'); + last_name = model.last_name || model.get('last_name'); + url = "/" + role + "s/" + id; + return "" + first_name + " " + last_name + ""; + }, + linkedVehicle: function(vehicle, vehicleType) { + return " " + (vehicleType != null ? vehicleType.get('make') : void 0) + " " + (vehicleType != null ? vehicleType.get('model') : void 0) + " " + (vehicle.get('year')) + " "; + }, + linkedUserId: function(userType, userId) { + return "" + userType + " " + userId + ""; + }, + timeDelta: function(start, end) { + var delta; + if (typeof start === 'string') { + start = this.parseDate(start); + } + if (typeof end === 'string') { + end = this.parseDate(end); + } + if (end && start) { + delta = end.getTime() - start.getTime(); + return this.formatSeconds(delta / 1000); + } else { + return '00:00'; + } + }, + formatSeconds: function(s) { + var minutes, seconds; + s = Math.floor(s); + minutes = Math.floor(s / 60); + seconds = s - minutes * 60; + return "" + (this.leadingZero(minutes)) + ":" + (this.leadingZero(seconds)); + }, + formatCurrency: function(strValue, reverseSign, currency) { + var currency_locale, lc, mf; + if (reverseSign == null) { + reverseSign = false; + } + if (currency == null) { + currency = null; + } + strValue = String(strValue); + if (reverseSign) { + strValue = ~strValue.indexOf('-') ? strValue.split('-').join('') : ['-', strValue].join(''); + } + currency_locale = i18n.currencyToLocale[currency]; + try { + if (!(currency_locale != null) || currency_locale === i18n.locale) { + return i18n.jsworld.mf.format(strValue); + } else { + lc = new jsworld.Locale(POSIX_LC[currency_locale]); + mf = new jsworld.MonetaryFormatter(lc); + return mf.format(strValue); + } + } catch (error) { + i18n.log(error); + return strValue; + } + }, + formatTripFare: function(trip, type) { + var _ref, _ref2; + if (type == null) { + type = "fare"; + } + if (!trip.get('fare')) { + return 'n/a'; + } + if (((_ref = trip.get('fare_breakdown_local')) != null ? _ref.currency : void 0) != null) { + return app.helpers.formatCurrency(trip.get("" + type + "_local"), false, (_ref2 = trip.get('fare_breakdown_local')) != null ? _ref2.currency : void 0); + } else if (trip.get("" + type + "_string") != null) { + return trip.get("" + type + "_string"); + } else if (trip.get("" + type + "_local") != null) { + return trip.get("" + type + "_local"); + } else { + return 'n/a'; + } + }, + formatPhoneNumber: function(phoneNumber, countryCode) { + if (countryCode == null) { + countryCode = "+1"; + } + if (phoneNumber != null) { + phoneNumber = String(phoneNumber); + switch (countryCode) { + case '+1': + return countryCode + ' ' + phoneNumber.substring(0, 3) + '-' + phoneNumber.substring(3, 6) + '-' + phoneNumber.substring(6, 10); + case '+33': + return countryCode + ' ' + phoneNumber.substring(0, 1) + ' ' + phoneNumber.substring(1, 3) + ' ' + phoneNumber.substring(3, 5) + ' ' + phoneNumber.substring(5, 7) + ' ' + phoneNumber.substring(7, 9); + default: + countryCode + phoneNumber; + } + } + return "" + countryCode + " " + phoneNumber; + }, + parseDate: function(d, cityTime, tz) { + var city_filter, parsed, _ref; + if (cityTime == null) { + cityTime = true; + } + if (tz == null) { + tz = null; + } + if (((_ref = !d.substr(-6, 1)) === '+' || _ref === '-') || d.length === 19) { + d += '+00:00'; + } + if (/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.test(d)) { + parsed = d.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/); + d = new Date(); + d.setUTCFullYear(parsed[1]); + d.setUTCMonth(parsed[2] - 1); + d.setUTCDate(parsed[3]); + d.setUTCHours(parsed[4]); + d.setUTCMinutes(parsed[5]); + d.setUTCSeconds(parsed[6]); + } else { + d = Date.parse(d); + } + if (typeof d === 'number') { + d = new Date(d); + } + d = new timezoneJS.Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), 'Etc/UTC'); + if (tz) { + d.convertToTimezone(tz); + } else if (cityTime) { + city_filter = $.cookie('city_filter'); + if (city_filter) { + tz = $("#city_filter option[value=" + city_filter + "]").attr('data-timezone'); + if (tz) { + d.convertToTimezone(tz); + } + } + } + return d; + }, + dateToTimezone: function(d) { + var city_filter, tz; + d = new timezoneJS.Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), 'Etc/UTC'); + city_filter = $.cookie('city_filter'); + if (city_filter) { + tz = $("#city_filter option[value=" + city_filter + "]").attr('data-timezone'); + d.convertToTimezone(tz); + } + return d; + }, + fixAMPM: function(d, formatted) { + if (d.hours >= 12) { + return formatted.replace(/\b[AP]M\b/, 'PM'); + } else { + return formatted.replace(/\b[AP]M\b/, 'AM'); + } + }, + formatDate: function(d, time, timezone) { + var formatted; + if (time == null) { + time = true; + } + if (timezone == null) { + timezone = null; + } + d = this.parseDate(d, true, timezone); + formatted = time ? ("" + (i18n.jsworld.dtf.formatDate(d)) + " ") + this.formatTime(d, d.getTimezoneInfo()) : i18n.jsworld.dtf.formatDate(d); + return this.fixAMPM(d, formatted); + }, + formatDateLong: function(d, time, timezone) { + if (time == null) { + time = true; + } + if (timezone == null) { + timezone = null; + } + d = this.parseDate(d, true, timezone); + timezone = d.getTimezoneInfo().tzAbbr; + if (time) { + return (i18n.jsworld.dtf.formatDateTime(d)) + (" " + timezone); + } else { + return i18n.jsworld.dtf.formatDate(d); + } + }, + formatTimezoneJSDate: function(d) { + var day, hours, jsDate, minutes, month, year; + year = d.getFullYear(); + month = this.leadingZero(d.getMonth()); + day = this.leadingZero(d.getDate()); + hours = this.leadingZero(d.getHours()); + minutes = this.leadingZero(d.getMinutes()); + jsDate = new Date(year, month, day, hours, minutes, 0); + return jsDate.toDateString(); + }, + formatTime: function(d, timezone) { + var formatted; + if (timezone == null) { + timezone = null; + } + formatted = ("" + (i18n.jsworld.dtf.formatTime(d))) + (timezone != null ? " " + (timezone != null ? timezone.tzAbbr : void 0) : ""); + return this.fixAMPM(d, formatted); + }, + formatISODate: function(d) { + var pad; + pad = function(n) { + if (n < 10) { + return '0' + n; + } + return n; + }; + return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-' + pad(d.getUTCDate()) + 'T' + pad(d.getUTCHours()) + ':' + pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds()) + 'Z'; + }, + formatExpDate: function(d) { + var month, year; + d = this.parseDate(d); + year = d.getFullYear(); + month = this.leadingZero(d.getMonth() + 1); + return "" + year + "-" + month; + }, + formatLatLng: function(lat, lng, precision) { + if (precision == null) { + precision = 8; + } + return parseFloat(lat).toFixed(precision) + ',' + parseFloat(lng).toFixed(precision); + }, + leadingZero: function(num) { + if (num < 10) { + return "0" + num; + } else { + return num; + } + }, + roundNumber: function(num, dec) { + return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec); + }, + notesToHTML: function(notes) { + var i, note, notesHTML, _i, _len; + notesHTML = ''; + i = 1; + if (notes) { + for (_i = 0, _len = notes.length; _i < _len; _i++) { + note = notes[_i]; + notesHTML += "" + note['userid'] + "     " + (this.formatDate(note['created_at'])) + "

" + note['note'] + "

"; + notesHTML += "
"; + } + } + return notesHTML.replace("'", '"e'); + }, + formatPhone: function(n) { + var parts, phone, regexObj; + n = "" + n; + regexObj = /^(?:\+?1[-. ]?)?(?:\(?([0-9]{3})\)?[-. ]?)?([0-9]{3})[-. ]?([0-9]{4})$/; + if (regexObj.test(n)) { + parts = n.match(regexObj); + phone = ""; + if (parts[1]) { + phone += "(" + parts[1] + ") "; + } + phone += "" + parts[2] + "-" + parts[3]; + } else { + phone = n; + } + return phone; + }, + usStates: ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'District of Columbia', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'], + onboardingPages: ['applied', 'ready_to_interview', 'pending_interview', 'interviewed', 'accepted', 'ready_to_onboard', 'pending_onboarding', 'active', 'waitlisted', 'rejected'], + driverBreadCrumb: function(loc, model) { + var onboardingPage, out, _i, _len, _ref; + out = "Drivers > "; + if (!(model != null)) { + out += ""; + } else { + out += "" + (this.onboardingUrlToName(model.get('driver_status'))) + ""; + out += " > " + (this.linkedName(model)) + " (" + (model.get('role')) + ") #" + (model.get('id')); + } + return out; + }, + onboardingUrlToName: function(url) { + return url != null ? url.replace(/_/g, " ").replace(/(^|\s)([a-z])/g, function(m, p1, p2) { + return p1 + p2.toUpperCase(); + }) : void 0; + }, + formatVehicle: function(vehicle) { + if (vehicle.get('make') && vehicle.get('model') && vehicle.get('license_plate')) { + return "" + (vehicle.get('make')) + " " + (vehicle.get('model')) + " (" + (vehicle.get('license_plate')) + ")"; + } + }, + docArbitraryFields: function(docName, cityDocs) { + var doc, field, out, _i, _j, _len, _len2, _ref; + out = ""; + for (_i = 0, _len = cityDocs.length; _i < _len; _i++) { + doc = cityDocs[_i]; + if (doc.name === docName && __indexOf.call(_.keys(doc), "metaFields") >= 0) { + _ref = doc.metaFields; + for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) { + field = _ref[_j]; + out += "" + field.label + ":
"; + } + } + } + return out; + }, + capitaliseFirstLetter: function(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + }, + createDocUploadForm: function(docName, driverId, vehicleId, cityMeta, vehicleName, expirationRequired) { + var ddocs, expDropdowns, pdocs, vdocs; + if (driverId == null) { + driverId = "None"; + } + if (vehicleId == null) { + vehicleId = "None"; + } + if (cityMeta == null) { + cityMeta = []; + } + if (vehicleName == null) { + vehicleName = false; + } + if (expirationRequired == null) { + expirationRequired = false; + } + ddocs = cityMeta["driverRequiredDocs"] || []; + pdocs = cityMeta["partnerRequiredDocs"] || []; + vdocs = cityMeta["vehicleRequiredDocs"] || []; + expDropdowns = "Expiration Date:\n -\n"; + return " \n
\n \n \n \n\n
\n " + (vehicleName ? vehicleName : "") + " " + docName + "\n
\n\n
\n \n
\n\n
\n " + (expirationRequired ? expDropdowns : "") + "\n
\n\n
\n " + (app.helpers.docArbitraryFields(docName, _.union(ddocs, pdocs, vdocs))) + "\n
\n\n
\n \n
\n\n
\n"; + }, + countrySelector: function(name, options) { + var countries, countryCodePrefix, defaultOptions; + if (options == null) { + options = {}; + } + defaultOptions = { + selectedKey: 'telephone_code', + selectedValue: '+1', + silent: false + }; + _.extend(defaultOptions, options); + options = defaultOptions; + countries = new app.collections.countries(); + countries.fetch({ + data: { + limit: 300 + }, + success: function(countries) { + var $option, $select, country, selected, _i, _len, _ref; + selected = false; + _ref = countries.models || []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + country = _ref[_i]; + $select = $("select[name=" + name + "]"); + $option = $('').val(country.id).attr('data-iso2', country.get('iso2')).attr('data-prefix', country.get('telephone_code')).html(country.get('name')); + if (country.get(options.selectedKey) === options.selectedValue && !selected) { + selected = true; + $option.attr('selected', 'selected'); + } + $select.append($option); + } + if (selected && !options.silent) { + return $select.val(options.selected).trigger('change'); + } + } + }); + countryCodePrefix = options.countryCodePrefix ? "data-country-code-prefix='" + options.countryCodePrefix + "'" : ''; + return ""; + }, + missingDocsOnDriver: function(driver) { + var city, docsReq, documents, partnerDocs; + city = driver.get('city'); + documents = driver.get('documents'); + if ((city != null) && (documents != null)) { + docsReq = _.pluck(city != null ? city.get('meta')["driverRequiredDocs"] : void 0, "name"); + if (driver.get('role') === "partner") { + partnerDocs = _.pluck(city != null ? city.get('meta')["partnerRequiredDocs"] : void 0, "name"); + docsReq = _.union(docsReq, partnerDocs); + } + return _.reject(docsReq, __bind(function(doc) { + return __indexOf.call((documents != null ? documents.pluck("name") : void 0) || [], doc) >= 0; + }, this)); + } else { + return []; + } + } + }; +}).call(this); +}, "web-lib/i18n": function(exports, require, module) {(function() { + exports.i18n = { + defaultLocale: 'en_US', + cookieName: '_LOCALE_', + locales: { + 'en_US': "English (US)", + 'fr_FR': "Français" + }, + currencyToLocale: { + 'USD': 'en_US', + 'EUR': 'fr_FR' + }, + logglyKey: 'd2d5a9bc-7ebe-4538-a180-81e62c705b1b', + logglyHost: 'https://logs.loggly.com', + init: function() { + this.castor = new window.loggly({ + url: this.logglyHost + '/inputs/' + this.logglyKey + '?rt=1', + level: 'error' + }); + this.setLocale($.cookie(this.cookieName) || this.defaultLocale); + window.t = _.bind(this.t, this); + this.loadLocaleTranslations(this.locale); + if (!(this[this.defaultLocale] != null)) { + return this.loadLocaleTranslations(this.defaultLocale); + } + }, + loadLocaleTranslations: function(locale) { + var loadPaths, path, _i, _len, _results; + loadPaths = ['web-lib/translations/' + locale, 'web-lib/translations/' + locale.slice(0, 2), 'translations/' + locale, 'translations/' + locale.slice(0, 2)]; + _results = []; + for (_i = 0, _len = loadPaths.length; _i < _len; _i++) { + path = loadPaths[_i]; + locale = path.substring(path.lastIndexOf('/') + 1); + if (this[locale] == null) { + this[locale] = {}; + } + _results.push((function() { + try { + return _.extend(this[locale], require(path).translations); + } catch (error) { + + } + }).call(this)); + } + return _results; + }, + getLocale: function() { + return this.locale; + }, + setLocale: function(locale) { + var message, parts, _ref; + parts = locale.split('_'); + this.locale = parts[0].toLowerCase(); + if (parts.length > 1) { + this.locale += "_" + (parts[1].toUpperCase()); + } + if (this.locale) { + $.cookie(this.cookieName, this.locale, { + path: '/', + domain: '.uber.com' + }); + } + try { + ((_ref = this.jsworld) != null ? _ref : this.jsworld = {}).lc = new jsworld.Locale(POSIX_LC[this.locale]); + this.jsworld.mf = new jsworld.MonetaryFormatter(this.jsworld.lc); + this.jsworld.nf = new jsworld.NumericFormatter(this.jsworld.lc); + this.jsworld.dtf = new jsworld.DateTimeFormatter(this.jsworld.lc); + this.jsworld.np = new jsworld.NumericParser(this.jsworld.lc); + this.jsworld.mp = new jsworld.MonetaryParser(this.jsworld.lc); + return this.jsworld.dtp = new jsworld.DateTimeParser(this.jsworld.lc); + } catch (error) { + message = 'JsWorld error with locale: ' + this.locale; + return this.log({ + message: message, + error: error + }); + } + }, + getTemplate: function(id) { + var _ref, _ref2; + return ((_ref = this[this.locale]) != null ? _ref[id] : void 0) || ((_ref2 = this[this.locale.slice(0, 2)]) != null ? _ref2[id] : void 0); + }, + getTemplateDefault: function(id) { + var _ref, _ref2; + return ((_ref = this[this.defaultLocale]) != null ? _ref[id] : void 0) || ((_ref2 = this[this.defaultLocale.slice(0, 2)]) != null ? _ref2[id] : void 0); + }, + getTemplateOrDefault: function(id) { + return this.getTemplate(id) || this.getTemplateDefault(id); + }, + t: function(id, vars) { + var errStr, locale, template; + if (vars == null) { + vars = {}; + } + locale = this.getLocale(); + template = this.getTemplate(id); + if (template == null) { + if (/dev|test/.test(window.location.host)) { + template = "(?) " + id; + } else { + template = this.getTemplateDefault(id); + } + errStr = "Missing [" + locale + "] translation for [" + id + "] at [" + window.location.hash + "] - Default template is [" + template + "]"; + this.log({ + error: errStr, + locale: locale, + id: id, + defaultTemplate: template + }); + } + if (template) { + return _.template(template, vars); + } else { + return id; + } + }, + log: function(error) { + if (/dev/.test(window.location.host)) { + if ((typeof console !== "undefined" && console !== null ? console.log : void 0) != null) { + return console.log(error); + } + } else { + _.extend(error, { + host: window.location.host, + hash: window.location.hash + }); + return this.castor.error(JSON.stringify(error)); + } + } + }; +}).call(this); +}, "web-lib/mixins/i18n_phone_form": function(exports, require, module) {(function() { + exports.i18nPhoneForm = { + _events: { + 'change select[data-country-code-prefix]': 'setCountryCodePrefix' + }, + setCountryCodePrefix: function(e) { + var $el, prefix; + $el = $(e.currentTarget); + prefix = $el.find('option:selected').attr('data-prefix'); + return $("#" + ($el.attr('data-country-code-prefix'))).text(prefix); + } + }; +}).call(this); +}, "web-lib/models/country": function(exports, require, module) {(function() { + var UberModel; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + UberModel = require('web-lib/uber_model').UberModel; + exports.Country = (function() { + __extends(Country, UberModel); + function Country() { + Country.__super__.constructor.apply(this, arguments); + } + Country.prototype.url = function() { + if (this.id) { + return "/countries/" + this.id; + } else { + return '/countries'; + } + }; + return Country; + })(); +}).call(this); +}, "web-lib/models/vehicle_type": function(exports, require, module) {(function() { + var UberModel; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + UberModel = require('web-lib/uber_model').UberModel; + exports.VehicleType = (function() { + __extends(VehicleType, UberModel); + function VehicleType() { + this.toString = __bind(this.toString, this); + VehicleType.__super__.constructor.apply(this, arguments); + } + VehicleType.prototype.endpoint = 'vehicle_types'; + VehicleType.prototype.toTableRow = function(cols) { + var actions, c, capacity, city_id, columnValues, created_at, created_by_user_id, deleted_at, id, make, minimum_year, model, rows, type, updated_at, updated_by_user_id, _i, _len, _ref; + id = "" + (this.get('id')) + ""; + if (this.get('created_at')) { + created_at = app.helpers.formatDate(this.get('created_at')); + } + if (this.get('updated_at')) { + updated_at = app.helpers.formatDate(this.get('updated_at')); + } + if (this.get('deleted_at')) { + deleted_at = app.helpers.formatDate(this.get('deleted_at')); + } + created_by_user_id = "" + (this.get('created_by_user_id')) + ""; + updated_by_user_id = "" + (this.get('updated_by_user_id')) + ""; + city_id = (_ref = this.get('city')) != null ? _ref.get('display_name') : void 0; + type = this.get('type'); + make = this.get('make'); + model = this.get('model'); + capacity = this.get('capacity'); + minimum_year = this.get('minimum_year'); + actions = "Show"; + if (!this.get('deleted_at')) { + actions += " Edit"; + actions += " Delete"; + } + columnValues = { + id: id, + created_at: created_at, + updated_at: updated_at, + deleted_at: deleted_at, + created_by_user_id: created_by_user_id, + updated_by_user_id: updated_by_user_id, + city_id: city_id, + type: type, + make: make, + model: model, + capacity: capacity, + minimum_year: minimum_year, + actions: actions + }; + rows = []; + for (_i = 0, _len = cols.length; _i < _len; _i++) { + c = cols[_i]; + rows.push(columnValues[c] ? columnValues[c] : '-'); + } + return rows; + }; + VehicleType.prototype.toString = function() { + return this.get('make') + ' ' + this.get('model') + ' ' + this.get('type') + (" (" + (this.get('capacity')) + ")"); + }; + return VehicleType; + })(); +}).call(this); +}, "web-lib/templates/footer": function(exports, require, module) {module.exports = function(__obj) { + if (!__obj) __obj = {}; + var __out = [], __capture = function(callback) { + var out = __out, result; + __out = []; + callback.call(this); + result = __out.join(''); + __out = out; + return __safe(result); + }, __sanitize = function(value) { + if (value && value.ecoSafe) { + return value; + } else if (typeof value !== 'undefined' && value != null) { + return __escape(value); + } else { + return ''; + } + }, __safe, __objSafe = __obj.safe, __escape = __obj.escape; + __safe = __obj.safe = function(value) { + if (value && value.ecoSafe) { + return value; + } else { + if (!(typeof value !== 'undefined' && value != null)) value = ''; + var result = new String(value); + result.ecoSafe = true; + return result; + } + }; + if (!__escape) { + __escape = __obj.escape = function(value) { + return ('' + value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + } + (function() { + (function() { + var locale, title, _ref; + __out.push('\n\n\n\n\n'); + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}}, "web-lib/translations/en": function(exports, require, module) {(function() { + exports.translations = { + "Info": "Info", + "Learn More": "Learn More", + "Pricing": "Pricing", + "FAQ": "FAQ", + "Support": "Support", + "Support & FAQ": "Support & FAQ", + "Contact Us": "Contact Us", + "Jobs": "Jobs", + "Phones": "Phones", + "Text Message": "Text Message", + "iPhone": "iPhone", + "Android": "Android", + "Drivers": "Drivers", + "Apply": "Apply", + "Sign In": "Sign In", + "Social": "Social", + "Twitter": "Twitter", + "Facebook": "Facebook", + "Blog": "Blog", + "Legal": "Legal", + "Company_Footer": "Company", + "Privacy Policy": "Privacy Policy", + "Terms": "Terms", + "Copyright © Uber Technologies, Inc.": "Copyright © Uber Technologies, Inc.", + "Language:": "Language:", + "Apply to Drive": "Apply to Drive", + "Expiration": "Expiration", + "Fare": "Fare", + "Driver": "Driver ", + "Dashboard": "Dashboard", + "Forgot Password": "Forgot Password", + "Trip Details": "Trip Details", + "Save": "Save", + "Cancel": "Cancel", + "Edit": "Edit", + "Password": "Password", + "First Name": "First Name", + "Last Name": "Last Name", + "Email Address": "Email Address", + "Submit": "Submit", + "Mobile Number": "Mobile Number", + "Zip Code": "Zip Code", + "Sign Out": "Sign Out", + "Confirm Email Message": "Attempting to confirm email...", + "Upload": "Upload", + "Rating": "Rating", + "Pickup Time": "Pickup Time", + "2011": "2011", + "2012": "2012", + "2013": "2013", + "2014": "2014", + "2015": "2015", + "2016": "2016", + "2017": "2017", + "2018": "2018", + "2019": "2019", + "2020": "2020", + "2021": "2021", + "2022": "2022", + "01": "01", + "02": "02", + "03": "03", + "04": "04", + "05": "05", + "06": "06", + "07": "07", + "08": "08", + "09": "09", + "10": "10", + "11": "11", + "12": "12" + }; +}).call(this); +}, "web-lib/translations/fr": function(exports, require, module) {(function() { + exports.translations = { + "Info": "Info", + "Learn More": "En Savoir Plus", + "Pricing": "Calcul du Prix", + "Support & FAQ": "Aide & FAQ", + "Contact Us": "Contactez Nous", + "Jobs": "Emplois", + "Phones": "Téléphones", + "Text Message": "SMS", + "iPhone": "iPhone", + "Android": "Android", + "Apply to Drive": "Candidature Chauffeur", + "Sign In": "Connexion", + "Social": "Contact", + "Twitter": "Twitter", + "Facebook": "Facebook", + "Blog": "Blog", + "Privacy Policy": "Protection des Données Personelles", + "Terms": "Conditions Générales", + "Copyright © Uber Technologies, Inc.": "© Uber, Inc.", + "Language:": "Langue:", + "Forgot Password": "Mot de passe oublié", + "Company_Footer": "À Propos d'Uber", + "Expiration": "Expiration", + "Fare": "Tarif", + "Driver": "Chauffeur", + "Drivers": "Chauffeurs", + "Dashboard": "Tableau de bord", + "Forgot Password": "Mot de passe oublié", + "Forgot Password?": "Mot de passe oublié?", + "Trip Details": "Détails de la course", + "Save": "Enregistrer", + "Cancel": "Annuler", + "Edit": "Modifier", + "Password": "Mot de passe", + "First Name": "Prénom", + "Last Name": "Nom", + "Email Address": "E-mail", + "Submit": "Soumettre", + "Mobile Number": "Téléphone Portable", + "Zip Code": "Code Postal", + "Sign Out": "Se déconnecter", + "Confirm Email Message": "E-mail de confirmation", + "Upload": "Télécharger", + "Rating": "Notation", + "Pickup Time": "Heure de prise en charge", + "2011": "2011", + "2012": "2012", + "2013": "2013", + "2014": "2014", + "2015": "2015", + "2016": "2016", + "2017": "2017", + "2018": "2018", + "2019": "2019", + "2020": "2020", + "2021": "2021", + "2022": "2022", + "01": "01", + "02": "02", + "03": "03", + "04": "04", + "05": "05", + "06": "06", + "07": "07", + "08": "08", + "09": "09", + "10": "10", + "11": "11", + "12": "12" + }; +}).call(this); +}, "web-lib/uber_collection": function(exports, require, module) {(function() { + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + exports.UberCollection = (function() { + __extends(UberCollection, Backbone.Collection); + function UberCollection() { + UberCollection.__super__.constructor.apply(this, arguments); + } + UberCollection.prototype.parse = function(data) { + var model, tmp, _i, _in, _len, _out; + _in = data.resources || data; + _out = []; + if (data.meta) { + this.meta = data.meta; + } + for (_i = 0, _len = _in.length; _i < _len; _i++) { + model = _in[_i]; + tmp = new this.model; + tmp.set(tmp.parse(model)); + _out.push(tmp); + } + return _out; + }; + UberCollection.prototype.isRenderable = function() { + if (this.models.length) { + return true; + } + }; + UberCollection.prototype.toTableRows = function(cols) { + var tableRows; + tableRows = []; + _.each(this.models, function(model) { + return tableRows.push(model.toTableRow(cols)); + }); + return tableRows; + }; + return UberCollection; + })(); +}).call(this); +}, "web-lib/uber_model": function(exports, require, module) {(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }, __indexOf = Array.prototype.indexOf || function(item) { + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === item) return i; + } + return -1; + }; + exports.UberModel = (function() { + __extends(UberModel, Backbone.Model); + function UberModel() { + this.refetch = __bind(this.refetch, this); + this.fetch = __bind(this.fetch, this); + this.save = __bind(this.save, this); + this.parse = __bind(this.parse, this); + UberModel.__super__.constructor.apply(this, arguments); + } + UberModel.prototype.endpoint = 'set_api_endpoint_in_subclass'; + UberModel.prototype.refetchOptions = {}; + UberModel.prototype.url = function(type) { + var endpoint_path; + endpoint_path = "/" + this.endpoint; + if (this.get('id')) { + return endpoint_path + ("/" + (this.get('id'))); + } else { + return endpoint_path; + } + }; + UberModel.prototype.isRenderable = function() { + var i, key, value, _ref; + i = 0; + _ref = this.attributes; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + value = _ref[key]; + if (this.attributes.hasOwnProperty(key)) { + i += 1; + } + if (i > 1) { + return true; + } + } + return !(i === 1); + }; + UberModel.prototype.parse = function(response) { + var attrs, key, model, models, _i, _j, _k, _len, _len2, _len3, _ref, _ref2; + if (typeof response === 'object') { + _ref = _.intersection(_.keys(app.models), _.keys(response)); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + if (response[key]) { + attrs = this.parse(response[key]); + if (typeof attrs === 'object') { + response[key] = new app.models[key](attrs); + } + } + } + _ref2 = _.intersection(_.keys(app.collections), _.keys(response)); + for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { + key = _ref2[_j]; + models = response[key]; + if (_.isArray(models)) { + response[key] = new app.collections[key]; + for (_k = 0, _len3 = models.length; _k < _len3; _k++) { + model = models[_k]; + attrs = app.collections[key].prototype.model.prototype.parse(model); + response[key].add(new response[key].model(attrs)); + } + } + } + } + return response; + }; + UberModel.prototype.save = function(attributes, options) { + var attr, _i, _j, _len, _len2, _ref, _ref2; + if (options == null) { + options = {}; + } + _ref = _.intersection(_.keys(app.models), _.keys(this.attributes)); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + attr = _ref[_i]; + if (typeof this.get(attr) === "object") { + this.unset(attr, { + silent: true + }); + } + } + _ref2 = _.intersection(_.keys(app.collections), _.keys(this.attributes)); + for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { + attr = _ref2[_j]; + if (typeof this.get(attr) === "object") { + this.unset(attr, { + silent: true + }); + } + } + if ((options != null) && options.diff && (attributes != null) && attributes !== {}) { + attributes['id'] = this.get('id'); + attributes['token'] = this.get('token'); + this.clear({ + 'silent': true + }); + this.set(attributes, { + silent: true + }); + } + if (__indexOf.call(_.keys(options), "data") < 0 && __indexOf.call(_.keys(this.refetchOptions || {}), "data") >= 0) { + options.data = this.refetchOptions.data; + } + return Backbone.Model.prototype.save.call(this, attributes, options); + }; + UberModel.prototype.fetch = function(options) { + this.refetchOptions = options; + return Backbone.Model.prototype.fetch.call(this, options); + }; + UberModel.prototype.refetch = function() { + return this.fetch(this.refetchOptions); + }; + return UberModel; + })(); +}).call(this); +}, "web-lib/uber_router": function(exports, require, module) {(function() { + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + exports.UberRouter = (function() { + __extends(UberRouter, Backbone.Router); + function UberRouter() { + UberRouter.__super__.constructor.apply(this, arguments); + } + UberRouter.prototype.datePickers = function(format) { + if (format == null) { + format = "%Z-%m-%dT%H:%i:%s%:"; + } + $('.datepicker').AnyTime_noPicker(); + return $('.datepicker').AnyTime_picker({ + 'format': format, + 'formatUtcOffset': '%@' + }); + }; + UberRouter.prototype.autoGrowInput = function() { + return $('.editable input').autoGrowInput(); + }; + UberRouter.prototype.windowTitle = function(title) { + return $(document).attr('title', title); + }; + return UberRouter; + })(); +}).call(this); +}, "web-lib/uber_show_view": function(exports, require, module) {(function() { + var UberView; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + UberView = require('web-lib/uber_view').UberView; + exports.UberShowView = (function() { + __extends(UberShowView, UberView); + function UberShowView() { + UberShowView.__super__.constructor.apply(this, arguments); + } + UberShowView.prototype.view = 'show'; + UberShowView.prototype.events = { + 'click #edit': 'edit', + 'submit form': 'save', + 'click .cancel': 'cancel' + }; + UberShowView.prototype.errors = null; + UberShowView.prototype.showTemplate = null; + UberShowView.prototype.editTemplate = null; + UberShowView.prototype.initialize = function() { + if (this.init_hook) { + this.init_hook(); + } + _.bindAll(this, 'render'); + return this.model.bind('change', this.render); + }; + UberShowView.prototype.render = function() { + var $el; + $el = $(this.el); + this.selectView(); + if (this.view === 'show') { + $el.html(this.showTemplate({ + model: this.model + })); + } else if (this.view === 'edit') { + $el.html(this.editTemplate({ + model: this.model, + errors: this.errors || {}, + collections: this.collections || {} + })); + } else { + $el.html(this.newTemplate({ + model: this.model, + errors: this.errors || {}, + collections: this.collections || {} + })); + } + if (this.render_hook) { + this.render_hook(); + } + this.errors = null; + this.userIdsToLinkedNames(); + this.datePickers(); + return this.place(); + }; + UberShowView.prototype.selectView = function() { + var url; + if (this.options.urlRendering) { + url = window.location.hash; + if (url.match(/\/new/)) { + return this.view = 'new'; + } else if (url.match(/\/edit/)) { + return this.view = 'edit'; + } else { + return this.view = 'show'; + } + } + }; + UberShowView.prototype.edit = function(e) { + e.preventDefault(); + if (this.options.urlRendering) { + window.location.hash = '#/' + this.model.endpoint + '/' + this.model.get('id') + '/edit'; + } else { + this.view = 'edit'; + } + return this.model.change(); + }; + UberShowView.prototype.save = function(e) { + var attributes, ele, form_attrs, _i, _len, _ref; + e.preventDefault(); + attributes = $(e.currentTarget).serializeToJson(); + form_attrs = {}; + _ref = $('input[type="radio"]'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + ele = _ref[_i]; + if ($(ele).is(':checked')) { + form_attrs[$(ele).attr('name')] = $(ele).attr('value'); + } + } + attributes = _.extend(attributes, form_attrs); + if (this.relationships) { + attributes = _.extend(attributes, { + relationships: this.relationships + }); + } + if (this.filter_attributes != null) { + this.filter_attributes(attributes); + } + return this.model.save(attributes, { + silent: true, + success: __bind(function(model) { + if (this.options.urlRendering) { + window.location.hash = '#/' + this.model.endpoint + '/' + this.model.get('id'); + } else { + this.view = 'show'; + } + return this.flash('success', "Uber save!"); + }, this), + statusCode: { + 406: __bind(function(xhr) { + this.errors = JSON.parse(xhr.responseText); + return this.flash('error', 'That was not Uber.'); + }, this) + }, + error: __bind(function(model, xhr) { + var code, message, responseJSON, responseText; + code = xhr.status; + responseText = xhr.responseText; + if (responseText) { + responseJSON = JSON.parse(responseText); + } + if (responseJSON && (typeof responseJSON === 'object') && (responseJSON.hasOwnProperty('error'))) { + message = responseJSON.error; + } + return this.flash('error', (code || 'Unknown') + ' error' + (': ' + message || '')); + }, this), + complete: __bind(function() { + return this.model.change(); + }, this) + }); + }; + UberShowView.prototype.cancel = function(e) { + e.preventDefault(); + if (this.options.urlRendering) { + window.location.hash = '#/' + this.model.endpoint + '/' + this.model.get('id'); + } else { + this.view = 'show'; + } + return this.model.fetch({ + silent: true, + complete: __bind(function() { + return this.model.change(); + }, this) + }); + }; + return UberShowView; + })(); +}).call(this); +}, "web-lib/uber_sync": function(exports, require, module) {(function() { + var methodType; + var __indexOf = Array.prototype.indexOf || function(item) { + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === item) return i; + } + return -1; + }; + methodType = { + create: 'POST', + update: 'PUT', + "delete": 'DELETE', + read: 'GET' + }; + exports.UberSync = function(method, model, options) { + var token; + options.type = methodType[method]; + options.url = _.isString(this.url) ? '/api' + this.url : '/api' + this.url(options.type); + options.data = _.extend({}, options.data); + if (__indexOf.call(_.keys(options.data), "city_id") < 0) { + if ($.cookie('city_filter')) { + _.extend(options.data, { + city_id: $.cookie('city_filter') + }); + } + } else { + delete options.data['city_id']; + } + if (options.type === 'POST' || options.type === 'PUT') { + _.extend(options.data, model.toJSON()); + } + token = $.cookie('token') ? $.cookie('token') : typeof USER !== "undefined" && USER !== null ? USER.get('token') : ""; + _.extend(options.data, { + token: token + }); + if (method === "delete") { + options.contentType = 'application/json'; + options.data = JSON.stringify(options.data); + } + return $.ajax(options); + }; +}).call(this); +}, "web-lib/uber_view": function(exports, require, module) {(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + exports.UberView = (function() { + __extends(UberView, Backbone.View); + function UberView() { + this.processDocumentUpload = __bind(this.processDocumentUpload, this); + UberView.__super__.constructor.apply(this, arguments); + } + UberView.prototype.className = 'view_container'; + UberView.prototype.hashId = function() { + return parseInt(location.hash.split('/')[2]); + }; + UberView.prototype.place = function(content) { + var $target; + $target = this.options.scope ? this.options.scope.find(this.options.selector) : $(this.options.selector); + $target[this.options.method || 'html'](content || this.el); + this.delegateEvents(); + $('#spinner').hide(); + return this; + }; + UberView.prototype.mixin = function(m, args) { + var events, self; + if (args == null) { + args = {}; + } + self = this; + events = m._events; + _.extend(this, m); + if (m.initialize) { + m.initialize(self, args); + } + return _.each(_.keys(events), function(key) { + var event, func, selector, split; + split = key.split(' '); + event = split[0]; + selector = split[1]; + func = events[key]; + return $(self.el).find(selector).live(event, function(e) { + return self[func](e); + }); + }); + }; + UberView.prototype.datePickers = function(format) { + if (format == null) { + format = "%Z-%m-%dT%H:%i:%s%:"; + } + $('.datepicker').AnyTime_noPicker(); + return $('.datepicker').AnyTime_picker({ + 'format': format, + 'formatUtcOffset': '%@' + }); + }; + UberView.prototype.dataTable = function(collection, selector, options, params, cols) { + var defaults; + if (selector == null) { + selector = 'table'; + } + if (options == null) { + options = {}; + } + if (params == null) { + params = {}; + } + if (cols == null) { + cols = []; + } + $(selector).empty(); + if (!cols.length) { + cols = collection.defaultColumns; + } + defaults = { + aoColumns: collection.tableColumns(cols), + bDestroy: true, + bSort: false, + bProcessing: true, + bFilter: false, + bServerSide: true, + bPaginate: true, + bScrollInfinite: true, + bScrollCollapse: true, + sScrollY: '600px', + iDisplayLength: 50, + fnServerData: function(source, data, callback) { + var defaultParams; + defaultParams = { + limit: data[4].value, + offset: data[3].value + }; + return collection.fetch({ + data: _.extend(defaultParams, params), + success: function() { + return callback({ + aaData: collection.toTableRows(cols), + iTotalRecords: collection.meta.count, + iTotalDisplayRecords: collection.meta.count + }); + }, + error: function() { + return new Error({ + message: 'Loading error.' + }); + } + }); + }, + fnRowCallback: function(nRow, aData, iDisplayIndex, iDisplayIndexFull) { + $('[data-tooltip]', nRow).qtip({ + content: { + attr: 'data-tooltip' + }, + style: { + classes: "ui-tooltip-light ui-tooltip-rounded ui-tooltip-shadow" + } + }); + return nRow; + } + }; + return $(this.el).find(selector).dataTable(_.extend(defaults, options)); + }; + UberView.prototype.dataTableLocal = function(collection, selector, options, params, cols) { + var $dataTable, defaults; + if (selector == null) { + selector = 'table'; + } + if (options == null) { + options = {}; + } + if (params == null) { + params = {}; + } + if (cols == null) { + cols = []; + } + $(selector).empty(); + if (!cols.length || cols.length === 0) { + cols = collection.defaultColumns; + } + defaults = { + aaData: collection.toTableRows(cols), + aoColumns: collection.tableColumns(cols), + bDestroy: true, + bSort: false, + bProcessing: true, + bFilter: false, + bScrollInfinite: true, + bScrollCollapse: true, + sScrollY: '600px', + iDisplayLength: -1 + }; + $dataTable = $(this.el).find(selector).dataTable(_.extend(defaults, options)); + _.delay(__bind(function() { + if ($dataTable && $dataTable.length > 0) { + return $dataTable.fnAdjustColumnSizing(); + } + }, this), 1); + return $dataTable; + }; + UberView.prototype.reverseGeocode = function() { + var $el; + return ''; + $el = $(this.el); + return this.requireMaps(function() { + var geocoder; + geocoder = new google.maps.Geocoder(); + return $el.find('[data-point]').each(function() { + var $this, latLng, point; + $this = $(this); + point = JSON.parse($this.attr('data-point')); + latLng = new google.maps.LatLng(point.latitude, point.longitude); + return geocoder.geocode({ + latLng: latLng + }, function(data, status) { + if (status === google.maps.GeocoderStatus.OK) { + return $this.text(data[0].formatted_address); + } + }); + }); + }); + }; + UberView.prototype.userIdsToLinkedNames = function() { + var $el; + $el = $(this.el); + return $el.find('a[data-user-id][data-user-type]').each(function() { + var $this, user, userType; + $this = $(this); + userType = $this.attr('data-user-type') === 'user' ? 'client' : $this.attr('data-user-type'); + user = new app.models[userType]({ + id: $this.attr('data-user-id') + }); + return user.fetch({ + success: function(user) { + return $this.html(app.helpers.linkedName(user)).attr('href', "!/" + user.role + "s/" + user.id); + }, + error: function() { + if ($this.attr('data-user-type') === 'user') { + user = new app.models['driver']({ + id: $this.attr('data-user-id') + }); + return user.fetch({ + success: function(user) { + return $this.html(app.helpers.linkedName(user)).attr('href', "!/driver/" + user.id); + } + }); + } + } + }); + }); + }; + UberView.prototype.selectedCity = function() { + var $selected, city, cityFilter; + cityFilter = $.cookie('city_filter'); + $selected = $("#city_filter option[value=" + cityFilter + "]"); + if (city_filter && $selected.length) { + return city = { + lat: parseFloat($selected.attr('data-lat')), + lng: parseFloat($selected.attr('data-lng')), + timezone: $selected.attr('data-timezone') + }; + } else { + return city = { + lat: 37.775, + lng: -122.45, + timezone: 'Etc/UTC' + }; + } + }; + UberView.prototype.updateModel = function(e, success) { + var $el, attrs, model, self; + e.preventDefault(); + $el = $(e.currentTarget); + self = this; + model = new this.model.__proto__.constructor({ + id: this.model.id + }); + attrs = {}; + $el.find('[name]').each(function() { + var $this; + $this = $(this); + return attrs["" + ($this.attr('name'))] = $this.val(); + }); + self.model.set(attrs); + $el.find('span.error').text(''); + return model.save(attrs, { + complete: function(xhr) { + var response; + response = JSON.parse(xhr.responseText); + switch (xhr.status) { + case 200: + self.model = model; + $el.find('[name]').val(''); + if (success) { + return success(); + } + break; + case 406: + return _.each(response, function(error, field) { + return $el.find("[name=" + field + "]").parent().find('span.error').text(error); + }); + default: + return this.unanticipatedError(response); + } + } + }); + }; + UberView.prototype.autoUpdateModel = function(e) { + var $el, arg, model, self, val; + $el = $(e.currentTarget); + val = $el.val(); + self = this; + if (val !== this.model.get($el.attr('id'))) { + arg = {}; + arg[$el.attr('id')] = $el.is(':checkbox') ? $el.is(':checked') ? 1 : 0 : val; + $('.editable span').empty(); + this.model.set(arg); + model = new this.model.__proto__.constructor({ + id: this.model.id + }); + return model.save(arg, { + complete: function(xhr) { + var key, response, _i, _len, _ref, _results; + response = JSON.parse(xhr.responseText); + switch (xhr.status) { + case 200: + self.flash('success', 'Saved!'); + return $el.blur(); + case 406: + self.flash('error', 'That was not Uber.'); + _ref = _.keys(response); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + _results.push($el.parent().find('span').html(response[key])); + } + return _results; + break; + default: + return self.unanticipatedError; + } + } + }); + } + }; + UberView.prototype.unanticipatedError = function(response) { + return self.flash('error', response); + }; + UberView.prototype.flash = function(type, text) { + var $banner; + $banner = $("." + type); + $banner.find('p').text(text).end().css('border', '1px solid #999').animate({ + top: 0 + }, 500); + return setTimeout(function() { + return $banner.animate({ + top: -$banner.outerHeight() + }, 500); + }, 3000); + }; + UberView.prototype.requireMaps = function(callback) { + if (typeof google !== 'undefined' && google.maps) { + return callback(); + } else { + return $.getScript("https://www.google.com/jsapi?key=" + CONFIG.googleJsApiKey, function() { + return google.load('maps', 3, { + callback: callback, + other_params: 'sensor=false&language=en' + }); + }); + } + }; + UberView.prototype.select_drop_down = function(model, key) { + var value; + value = model.get(key); + if (value) { + return $("select[id='" + key + "'] option[value='" + value + "']").attr('selected', 'selected'); + } + }; + UberView.prototype.processDocumentUpload = function(e) { + var $fi, $form, arbData, curDate, data, expDate, expM, expY, expiration, fileElementId, invalid; + e.preventDefault(); + $form = $(e.currentTarget); + $fi = $("input[type=file]", $form); + $(".validationError").removeClass("validationError"); + if (!$fi.val()) { + return $fi.addClass("validationError"); + } else { + fileElementId = $fi.attr('id'); + expY = $("select[name=expiration-year]", $form).val(); + expM = $("select[name=expiration-month]", $form).val(); + invalid = false; + if (expY && expM) { + expDate = new Date(expY, expM, 28); + curDate = new Date(); + if (expDate < curDate) { + invalid = true; + $(".expiration", $form).addClass("validationError"); + } + expiration = "" + expY + "-" + expM + "-28T23:59:59Z"; + } + arbData = {}; + $(".arbitraryField", $form).each(__bind(function(i, e) { + arbData[$(e).attr('name')] = $(e).val(); + if ($(e).val() === "") { + invalid = true; + return $(e).addClass("validationError"); + } + }, this)); + if (!invalid) { + data = { + token: $.cookie('token') || USER.get('token'), + name: $("input[name=fileName]", $form).val(), + meta: escape(JSON.stringify(arbData)), + user_id: $("input[name=driver_id]", $form).val(), + vehicle_id: $("input[name=vehicle_id]", $form).val() + }; + if (expiration) { + data['expiration'] = expiration; + } + $("#spinner").show(); + return $.ajaxFileUpload({ + url: '/api/documents', + secureuri: false, + fileElementId: fileElementId, + data: data, + complete: __bind(function(resp, status) { + var key, _i, _len, _ref, _results; + $("#spinner").hide(); + if (status === "success") { + if (this.model) { + this.model.refetch(); + } else { + USER.refetch(); + } + } + if (status === "error") { + _ref = _.keys(resp); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + _results.push($("*[name=" + key + "]", $form).addClass("validationError")); + } + return _results; + } + }, this) + }); + } + } + }; + return UberView; + })(); +}).call(this); +}, "web-lib/views/footer": function(exports, require, module) {(function() { + var footerTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + footerTemplate = require('web-lib/templates/footer'); + exports.SharedFooterView = (function() { + __extends(SharedFooterView, Backbone.View); + function SharedFooterView() { + SharedFooterView.__super__.constructor.apply(this, arguments); + } + SharedFooterView.prototype.id = 'footer_view'; + SharedFooterView.prototype.events = { + 'click .language': 'intl_set_cookie_locale' + }; + SharedFooterView.prototype.render = function() { + $(this.el).html(footerTemplate()); + this.delegateEvents(); + return this; + }; + SharedFooterView.prototype.intl_set_cookie_locale = function(e) { + var _ref; + i18n.setLocale(e != null ? (_ref = e.srcElement) != null ? _ref.id : void 0 : void 0); + return location.reload(); + }; + return SharedFooterView; + })(); +}).call(this); +}}); diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/embed-tokens.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/embed-tokens.js new file mode 100755 index 0000000..61307ee --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/embed-tokens.js @@ -0,0 +1,15 @@ +#! /usr/bin/env node + +global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util"); +var fs = require("fs"); +var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js + jsp = uglify.parser, + pro = uglify.uglify; + +var code = fs.readFileSync("embed-tokens.js", "utf8").replace(/^#.*$/mg, ""); +var ast = jsp.parse(code, null, true); + +// trololo +function fooBar() {} + +console.log(sys.inspect(ast, null, null)); diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/goto.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/goto.js new file mode 100644 index 0000000..945960c --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/goto.js @@ -0,0 +1,26 @@ +function unique(arqw) { + var a = [], i, j + outer: for (i = 0; i < arqw.length; i++) { + for (j = 0; j < a.length; j++) { + if (a[j] == arqw[i]) { + continue outer + } + } + a[a.length] = arqw[i] + } + return a +} + + +function unique(arqw) { + var crap = [], i, j + outer: for (i = 0; i < arqw.length; i++) { + for (j = 0; j < crap.length; j++) { + if (crap[j] == arqw[i]) { + continue outer + } + } + crap[crap.length] = arqw[i] + } + return crap +} diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/goto2.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/goto2.js new file mode 100644 index 0000000..d13b2bc --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/goto2.js @@ -0,0 +1,8 @@ +function q(qooo) { + var a; + foo: for(;;) { + a++; + if (something) break foo; + return qooo; + } +} diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/hoist.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/hoist.js new file mode 100644 index 0000000..4bf2b94 --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/hoist.js @@ -0,0 +1,33 @@ +function foo(arg1, arg2, arg3, arg4, arg5, arg6) { + var a = 5; + { + var d = 10, mak = 20, buz = 30; + var q = buz * 2; + } + if (moo) { + var a, b, c; + } + for (var arg1 = 0, d = 20; arg1 < 10; ++arg1) + console.log(arg3); + for (var i in mak) {} + for (j in d) {} + var d; + + function test() { + + }; + + //test(); + + (function moo(first, second){ + console.log(first); + })(1); + + (function moo(first, second){ + console.log(moo()); + })(1); +} + + +var foo; +var bar; diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/instrument.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/instrument.js new file mode 100644 index 0000000..c6a9d79 --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/instrument.js @@ -0,0 +1,97 @@ +// sample on how to use the parser and walker API to instrument some code + +var jsp = require("uglify-js").parser; +var pro = require("uglify-js").uglify; + +function instrument(code) { + var ast = jsp.parse(code, false, true); // true for the third arg specifies that we want + // to have start/end tokens embedded in the + // statements + var w = pro.ast_walker(); + + // we're gonna need this to push elements that we're currently looking at, to avoid + // endless recursion. + var analyzing = []; + function do_stat() { + var ret; + if (this[0].start && analyzing.indexOf(this) < 0) { + // without the `analyzing' hack, w.walk(this) would re-enter here leading + // to infinite recursion + analyzing.push(this); + ret = [ "splice", // XXX: "block" is safer + [ [ "stat", + [ "call", [ "name", "trace" ], + [ [ "string", this[0].toString() ], + [ "num", this[0].start.line ], + [ "num", this[0].start.col ], + [ "num", this[0].end.line ], + [ "num", this[0].end.col ]]]], + w.walk(this) ]]; + analyzing.pop(this); + } + return ret; + }; + var new_ast = w.with_walkers({ + "stat" : do_stat, + "label" : do_stat, + "break" : do_stat, + "continue" : do_stat, + "debugger" : do_stat, + "var" : do_stat, + "const" : do_stat, + "return" : do_stat, + "throw" : do_stat, + "try" : do_stat, + "defun" : do_stat, + "if" : do_stat, + "while" : do_stat, + "do" : do_stat, + "for" : do_stat, + "for-in" : do_stat, + "switch" : do_stat, + "with" : do_stat + }, function(){ + return w.walk(ast); + }); + return pro.gen_code(new_ast, { beautify: true }); +} + + + + +////// test code follows. + +var code = instrument(test.toString()); +console.log(code); + +function test() { + // simple stats + a = 5; + c += a + b; + "foo"; + + // var + var foo = 5; + const bar = 6, baz = 7; + + // switch block. note we can't track case lines the same way. + switch ("foo") { + case "foo": + return 1; + case "bar": + return 2; + } + + // for/for in + for (var i = 0; i < 5; ++i) { + console.log("Hello " + i); + } + for (var i in [ 1, 2, 3]) { + console.log(i); + } + + // note however that the following is broken. I guess we + // should add the block brackets in this case... + for (var i = 0; i < 5; ++i) + console.log("foo"); +} diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/instrument2.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/instrument2.js new file mode 100644 index 0000000..6aee5f3 --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/instrument2.js @@ -0,0 +1,138 @@ +// sample on how to use the parser and walker API to instrument some code + +var jsp = require("uglify-js").parser; +var pro = require("uglify-js").uglify; + +function instrument(code) { + var ast = jsp.parse(code, false, true); // true for the third arg specifies that we want + // to have start/end tokens embedded in the + // statements + var w = pro.ast_walker(); + + function trace (line, comment) { + var code = pro.gen_code(line, { beautify: true }); + var data = line[0] + + var args = [] + if (!comment) comment = "" + if (typeof data === "object") { + code = code.split(/\n/).shift() + args = [ [ "string", data.toString() ], + [ "string", code ], + [ "num", data.start.line ], + [ "num", data.start.col ], + [ "num", data.end.line ], + [ "num", data.end.col ]] + } else { + args = [ [ "string", data ], + [ "string", code ]] + + } + return [ "call", [ "name", "trace" ], args ]; + } + + // we're gonna need this to push elements that we're currently looking at, to avoid + // endless recursion. + var analyzing = []; + function do_stat() { + var ret; + if (this[0].start && analyzing.indexOf(this) < 0) { + // without the `analyzing' hack, w.walk(this) would re-enter here leading + // to infinite recursion + analyzing.push(this); + ret = [ "splice", + [ [ "stat", trace(this) ], + w.walk(this) ]]; + analyzing.pop(this); + } + return ret; + } + + function do_cond(c, t, f) { + return [ this[0], w.walk(c), + ["seq", trace(t), w.walk(t) ], + ["seq", trace(f), w.walk(f) ]]; + } + + function do_binary(c, l, r) { + if (c !== "&&" && c !== "||") { + return [this[0], c, w.walk(l), w.walk(r)]; + } + return [ this[0], c, + ["seq", trace(l), w.walk(l) ], + ["seq", trace(r), w.walk(r) ]]; + } + + var new_ast = w.with_walkers({ + "stat" : do_stat, + "label" : do_stat, + "break" : do_stat, + "continue" : do_stat, + "debugger" : do_stat, + "var" : do_stat, + "const" : do_stat, + "return" : do_stat, + "throw" : do_stat, + "try" : do_stat, + "defun" : do_stat, + "if" : do_stat, + "while" : do_stat, + "do" : do_stat, + "for" : do_stat, + "for-in" : do_stat, + "switch" : do_stat, + "with" : do_stat, + "conditional" : do_cond, + "binary" : do_binary + }, function(){ + return w.walk(ast); + }); + return pro.gen_code(new_ast, { beautify: true }); +} + + +////// test code follows. + +var code = instrument(test.toString()); +console.log(code); + +function test() { + // simple stats + a = 5; + c += a + b; + "foo"; + + // var + var foo = 5; + const bar = 6, baz = 7; + + // switch block. note we can't track case lines the same way. + switch ("foo") { + case "foo": + return 1; + case "bar": + return 2; + } + + // for/for in + for (var i = 0; i < 5; ++i) { + console.log("Hello " + i); + } + for (var i in [ 1, 2, 3]) { + console.log(i); + } + + for (var i = 0; i < 5; ++i) + console.log("foo"); + + for (var i = 0; i < 5; ++i) { + console.log("foo"); + } + + var k = plurp() ? 1 : 0; + var x = a ? doX(y) && goZoo("zoo") + : b ? blerg({ x: y }) + : null; + + var x = X || Y; +} diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/liftvars.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/liftvars.js new file mode 100644 index 0000000..2f4b7fe --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/liftvars.js @@ -0,0 +1,8 @@ +var UNUSED_VAR1 = 19; + +function main() { + var unused_var2 = 20; + alert(100); +} + +main(); diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/test.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/test.js new file mode 100755 index 0000000..f295fba --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/test.js @@ -0,0 +1,30 @@ +#! /usr/bin/env node + +global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util"); +var fs = require("fs"); +var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js + jsp = uglify.parser, + pro = uglify.uglify; + +var code = fs.readFileSync("hoist.js", "utf8"); +var ast = jsp.parse(code); + +ast = pro.ast_lift_variables(ast); + +var w = pro.ast_walker(); +ast = w.with_walkers({ + "function": function() { + var node = w.dive(this); // walk depth first + console.log(pro.gen_code(node, { beautify: true })); + return node; + }, + "name": function(name) { + return [ this[0], "X" ]; + } +}, function(){ + return w.walk(ast); +}); + +console.log(pro.gen_code(ast, { + beautify: true +})); diff --git a/node_modules/socket.io-client/node_modules/uglify-js/tmp/uglify-hangs.js b/node_modules/socket.io-client/node_modules/uglify-js/tmp/uglify-hangs.js new file mode 100644 index 0000000..0d5b7e0 --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/tmp/uglify-hangs.js @@ -0,0 +1,3930 @@ +/** + * @fileoverview + * + * JsWorld + * + *

Javascript library for localised formatting and parsing of: + *

    + *
  • Numbers + *
  • Dates and times + *
  • Currency + *
+ * + *

The library classes are configured with standard POSIX locale definitions + * derived from Unicode's Common Locale Data Repository (CLDR). + * + *

Website: JsWorld + * + * @author Vladimir Dzhuvinov + * @version 2.5 (2011-12-23) + */ + + + +/** + * @namespace Namespace container for the JsWorld library objects. + */ +jsworld = {}; + + +/** + * @function + * + * @description Formats a JavaScript Date object as an ISO-8601 date/time + * string. + * + * @param {Date} [d] A valid JavaScript Date object. If undefined the + * current date/time will be used. + * @param {Boolean} [withTZ] Include timezone offset, default false. + * + * @returns {String} The date/time formatted as YYYY-MM-DD HH:MM:SS. + */ +jsworld.formatIsoDateTime = function(d, withTZ) { + + if (typeof d === "undefined") + d = new Date(); // now + + if (typeof withTZ === "undefined") + withTZ = false; + + var s = jsworld.formatIsoDate(d) + " " + jsworld.formatIsoTime(d); + + if (withTZ) { + + var diff = d.getHours() - d.getUTCHours(); + var hourDiff = Math.abs(diff); + + var minuteUTC = d.getUTCMinutes(); + var minute = d.getMinutes(); + + if (minute != minuteUTC && minuteUTC < 30 && diff < 0) + hourDiff--; + + if (minute != minuteUTC && minuteUTC > 30 && diff > 0) + hourDiff--; + + var minuteDiff; + if (minute != minuteUTC) + minuteDiff = ":30"; + else + minuteDiff = ":00"; + + var timezone; + if (hourDiff < 10) + timezone = "0" + hourDiff + minuteDiff; + + else + timezone = "" + hourDiff + minuteDiff; + + if (diff < 0) + timezone = "-" + timezone; + + else + timezone = "+" + timezone; + + s = s + timezone; + } + + return s; +}; + + +/** + * @function + * + * @description Formats a JavaScript Date object as an ISO-8601 date string. + * + * @param {Date} [d] A valid JavaScript Date object. If undefined the current + * date will be used. + * + * @returns {String} The date formatted as YYYY-MM-DD. + */ +jsworld.formatIsoDate = function(d) { + + if (typeof d === "undefined") + d = new Date(); // now + + var year = d.getFullYear(); + var month = d.getMonth() + 1; + var day = d.getDate(); + + return year + "-" + jsworld._zeroPad(month, 2) + "-" + jsworld._zeroPad(day, 2); +}; + + +/** + * @function + * + * @description Formats a JavaScript Date object as an ISO-8601 time string. + * + * @param {Date} [d] A valid JavaScript Date object. If undefined the current + * time will be used. + * + * @returns {String} The time formatted as HH:MM:SS. + */ +jsworld.formatIsoTime = function(d) { + + if (typeof d === "undefined") + d = new Date(); // now + + var hour = d.getHours(); + var minute = d.getMinutes(); + var second = d.getSeconds(); + + return jsworld._zeroPad(hour, 2) + ":" + jsworld._zeroPad(minute, 2) + ":" + jsworld._zeroPad(second, 2); +}; + + +/** + * @function + * + * @description Parses an ISO-8601 formatted date/time string to a JavaScript + * Date object. + * + * @param {String} isoDateTimeVal An ISO-8601 formatted date/time string. + * + *

Accepted formats: + * + *

    + *
  • YYYY-MM-DD HH:MM:SS + *
  • YYYYMMDD HHMMSS + *
  • YYYY-MM-DD HHMMSS + *
  • YYYYMMDD HH:MM:SS + *
+ * + * @returns {Date} The corresponding Date object. + * + * @throws Error on a badly formatted date/time string or on a invalid date. + */ +jsworld.parseIsoDateTime = function(isoDateTimeVal) { + + if (typeof isoDateTimeVal != "string") + throw "Error: The parameter must be a string"; + + // First, try to match "YYYY-MM-DD HH:MM:SS" format + var matches = isoDateTimeVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)/); + + // If unsuccessful, try to match "YYYYMMDD HHMMSS" format + if (matches === null) + matches = isoDateTimeVal.match(/^(\d\d\d\d)(\d\d)(\d\d)[T ](\d\d)(\d\d)(\d\d)/); + + // ... try to match "YYYY-MM-DD HHMMSS" format + if (matches === null) + matches = isoDateTimeVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d)(\d\d)(\d\d)/); + + // ... try to match "YYYYMMDD HH:MM:SS" format + if (matches === null) + matches = isoDateTimeVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)/); + + // Report bad date/time string + if (matches === null) + throw "Error: Invalid ISO-8601 date/time string"; + + // Force base 10 parse int as some values may have leading zeros! + // (to avoid implicit octal base conversion) + var year = parseInt(matches[1], 10); + var month = parseInt(matches[2], 10); + var day = parseInt(matches[3], 10); + + var hour = parseInt(matches[4], 10); + var mins = parseInt(matches[5], 10); + var secs = parseInt(matches[6], 10); + + // Simple value range check, leap years not checked + // Note: the originial ISO time spec for leap hours (24:00:00) and seconds (00:00:60) is not supported + if (month < 1 || month > 12 || + day < 1 || day > 31 || + hour < 0 || hour > 23 || + mins < 0 || mins > 59 || + secs < 0 || secs > 59 ) + + throw "Error: Invalid ISO-8601 date/time value"; + + var d = new Date(year, month - 1, day, hour, mins, secs); + + // Check if the input date was valid + // (JS Date does automatic forward correction) + if (d.getDate() != day || d.getMonth() +1 != month) + throw "Error: Invalid date"; + + return d; +}; + + +/** + * @function + * + * @description Parses an ISO-8601 formatted date string to a JavaScript + * Date object. + * + * @param {String} isoDateVal An ISO-8601 formatted date string. + * + *

Accepted formats: + * + *

    + *
  • YYYY-MM-DD + *
  • YYYYMMDD + *
+ * + * @returns {Date} The corresponding Date object. + * + * @throws Error on a badly formatted date string or on a invalid date. + */ +jsworld.parseIsoDate = function(isoDateVal) { + + if (typeof isoDateVal != "string") + throw "Error: The parameter must be a string"; + + // First, try to match "YYYY-MM-DD" format + var matches = isoDateVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)/); + + // If unsuccessful, try to match "YYYYMMDD" format + if (matches === null) + matches = isoDateVal.match(/^(\d\d\d\d)(\d\d)(\d\d)/); + + // Report bad date/time string + if (matches === null) + throw "Error: Invalid ISO-8601 date string"; + + // Force base 10 parse int as some values may have leading zeros! + // (to avoid implicit octal base conversion) + var year = parseInt(matches[1], 10); + var month = parseInt(matches[2], 10); + var day = parseInt(matches[3], 10); + + // Simple value range check, leap years not checked + if (month < 1 || month > 12 || + day < 1 || day > 31 ) + + throw "Error: Invalid ISO-8601 date value"; + + var d = new Date(year, month - 1, day); + + // Check if the input date was valid + // (JS Date does automatic forward correction) + if (d.getDate() != day || d.getMonth() +1 != month) + throw "Error: Invalid date"; + + return d; +}; + + +/** + * @function + * + * @description Parses an ISO-8601 formatted time string to a JavaScript + * Date object. + * + * @param {String} isoTimeVal An ISO-8601 formatted time string. + * + *

Accepted formats: + * + *

    + *
  • HH:MM:SS + *
  • HHMMSS + *
+ * + * @returns {Date} The corresponding Date object, with year, month and day set + * to zero. + * + * @throws Error on a badly formatted time string. + */ +jsworld.parseIsoTime = function(isoTimeVal) { + + if (typeof isoTimeVal != "string") + throw "Error: The parameter must be a string"; + + // First, try to match "HH:MM:SS" format + var matches = isoTimeVal.match(/^(\d\d):(\d\d):(\d\d)/); + + // If unsuccessful, try to match "HHMMSS" format + if (matches === null) + matches = isoTimeVal.match(/^(\d\d)(\d\d)(\d\d)/); + + // Report bad date/time string + if (matches === null) + throw "Error: Invalid ISO-8601 date/time string"; + + // Force base 10 parse int as some values may have leading zeros! + // (to avoid implicit octal base conversion) + var hour = parseInt(matches[1], 10); + var mins = parseInt(matches[2], 10); + var secs = parseInt(matches[3], 10); + + // Simple value range check, leap years not checked + if (hour < 0 || hour > 23 || + mins < 0 || mins > 59 || + secs < 0 || secs > 59 ) + + throw "Error: Invalid ISO-8601 time value"; + + return new Date(0, 0, 0, hour, mins, secs); +}; + + +/** + * @private + * + * @description Trims leading and trailing whitespace from a string. + * + *

Used non-regexp the method from http://blog.stevenlevithan.com/archives/faster-trim-javascript + * + * @param {String} str The string to trim. + * + * @returns {String} The trimmed string. + */ +jsworld._trim = function(str) { + + var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000'; + + for (var i = 0; i < str.length; i++) { + + if (whitespace.indexOf(str.charAt(i)) === -1) { + str = str.substring(i); + break; + } + } + + for (i = str.length - 1; i >= 0; i--) { + if (whitespace.indexOf(str.charAt(i)) === -1) { + str = str.substring(0, i + 1); + break; + } + } + + return whitespace.indexOf(str.charAt(0)) === -1 ? str : ''; +}; + + + +/** + * @private + * + * @description Returns true if the argument represents a decimal number. + * + * @param {Number|String} arg The argument to test. + * + * @returns {Boolean} true if the argument represents a decimal number, + * otherwise false. + */ +jsworld._isNumber = function(arg) { + + if (typeof arg == "number") + return true; + + if (typeof arg != "string") + return false; + + // ensure string + var s = arg + ""; + + return (/^-?(\d+|\d*\.\d+)$/).test(s); +}; + + +/** + * @private + * + * @description Returns true if the argument represents a decimal integer. + * + * @param {Number|String} arg The argument to test. + * + * @returns {Boolean} true if the argument represents an integer, otherwise + * false. + */ +jsworld._isInteger = function(arg) { + + if (typeof arg != "number" && typeof arg != "string") + return false; + + // convert to string + var s = arg + ""; + + return (/^-?\d+$/).test(s); +}; + + +/** + * @private + * + * @description Returns true if the argument represents a decimal float. + * + * @param {Number|String} arg The argument to test. + * + * @returns {Boolean} true if the argument represents a float, otherwise false. + */ +jsworld._isFloat = function(arg) { + + if (typeof arg != "number" && typeof arg != "string") + return false; + + // convert to string + var s = arg + ""; + + return (/^-?\.\d+?$/).test(s); +}; + + +/** + * @private + * + * @description Checks if the specified formatting option is contained + * within the options string. + * + * @param {String} option The option to search for. + * @param {String} optionsString The options string. + * + * @returns {Boolean} true if the flag is found, else false + */ +jsworld._hasOption = function(option, optionsString) { + + if (typeof option != "string" || typeof optionsString != "string") + return false; + + if (optionsString.indexOf(option) != -1) + return true; + else + return false; +}; + + +/** + * @private + * + * @description String replacement function. + * + * @param {String} s The string to work on. + * @param {String} target The string to search for. + * @param {String} replacement The replacement. + * + * @returns {String} The new string. + */ +jsworld._stringReplaceAll = function(s, target, replacement) { + + var out; + + if (target.length == 1 && replacement.length == 1) { + // simple char/char case somewhat faster + out = ""; + + for (var i = 0; i < s.length; i++) { + + if (s.charAt(i) == target.charAt(0)) + out = out + replacement.charAt(0); + else + out = out + s.charAt(i); + } + + return out; + } + else { + // longer target and replacement strings + out = s; + + var index = out.indexOf(target); + + while (index != -1) { + + out = out.replace(target, replacement); + + index = out.indexOf(target); + } + + return out; + } +}; + + +/** + * @private + * + * @description Tests if a string starts with the specified substring. + * + * @param {String} testedString The string to test. + * @param {String} sub The string to match. + * + * @returns {Boolean} true if the test succeeds. + */ +jsworld._stringStartsWith = function (testedString, sub) { + + if (testedString.length < sub.length) + return false; + + for (var i = 0; i < sub.length; i++) { + if (testedString.charAt(i) != sub.charAt(i)) + return false; + } + + return true; +}; + + +/** + * @private + * + * @description Gets the requested precision from an options string. + * + *

Example: ".3" returns 3 decimal places precision. + * + * @param {String} optionsString The options string. + * + * @returns {integer Number} The requested precision, -1 if not specified. + */ +jsworld._getPrecision = function (optionsString) { + + if (typeof optionsString != "string") + return -1; + + var m = optionsString.match(/\.(\d)/); + if (m) + return parseInt(m[1], 10); + else + return -1; +}; + + +/** + * @private + * + * @description Takes a decimal numeric amount (optionally as string) and + * returns its integer and fractional parts packed into an object. + * + * @param {Number|String} amount The amount, e.g. "123.45" or "-56.78" + * + * @returns {object} Parsed amount object with properties: + * {String} integer : the integer part + * {String} fraction : the fraction part + */ +jsworld._splitNumber = function (amount) { + + if (typeof amount == "number") + amount = amount + ""; + + var obj = {}; + + // remove negative sign + if (amount.charAt(0) == "-") + amount = amount.substring(1); + + // split amount into integer and decimal parts + var amountParts = amount.split("."); + if (!amountParts[1]) + amountParts[1] = ""; // we need "" instead of null + + obj.integer = amountParts[0]; + obj.fraction = amountParts[1]; + + return obj; +}; + + +/** + * @private + * + * @description Formats the integer part using the specified grouping + * and thousands separator. + * + * @param {String} intPart The integer part of the amount, as string. + * @param {String} grouping The grouping definition. + * @param {String} thousandsSep The thousands separator. + * + * @returns {String} The formatted integer part. + */ +jsworld._formatIntegerPart = function (intPart, grouping, thousandsSep) { + + // empty separator string? no grouping? + // -> return immediately with no formatting! + if (thousandsSep == "" || grouping == "-1") + return intPart; + + // turn the semicolon-separated string of integers into an array + var groupSizes = grouping.split(";"); + + // the formatted output string + var out = ""; + + // the intPart string position to process next, + // start at string end, e.g. "10000000 0) { + + // get next group size (if any, otherwise keep last) + if (groupSizes.length > 0) + size = parseInt(groupSizes.shift(), 10); + + // int parse error? + if (isNaN(size)) + throw "Error: Invalid grouping"; + + // size is -1? -> no more grouping, so just copy string remainder + if (size == -1) { + out = intPart.substring(0, pos) + out; + break; + } + + pos -= size; // move to next sep. char. position + + // position underrun? -> just copy string remainder + if (pos < 1) { + out = intPart.substring(0, pos + size) + out; + break; + } + + // extract group and apply sep. char. + out = thousandsSep + intPart.substring(pos, pos + size) + out; + } + + return out; +}; + + +/** + * @private + * + * @description Formats the fractional part to the specified decimal + * precision. + * + * @param {String} fracPart The fractional part of the amount + * @param {integer Number} precision The desired decimal precision + * + * @returns {String} The formatted fractional part. + */ +jsworld._formatFractionPart = function (fracPart, precision) { + + // append zeroes up to precision if necessary + for (var i=0; fracPart.length < precision; i++) + fracPart = fracPart + "0"; + + return fracPart; +}; + + +/** + * @private + * + * @desription Converts a number to string and pad it with leading zeroes if the + * string is shorter than length. + * + * @param {integer Number} number The number value subjected to selective padding. + * @param {integer Number} length If the number has fewer digits than this length + * apply padding. + * + * @returns {String} The formatted string. + */ +jsworld._zeroPad = function(number, length) { + + // ensure string + var s = number + ""; + + while (s.length < length) + s = "0" + s; + + return s; +}; + + +/** + * @private + * @description Converts a number to string and pads it with leading spaces if + * the string is shorter than length. + * + * @param {integer Number} number The number value subjected to selective padding. + * @param {integer Number} length If the number has fewer digits than this length + * apply padding. + * + * @returns {String} The formatted string. + */ +jsworld._spacePad = function(number, length) { + + // ensure string + var s = number + ""; + + while (s.length < length) + s = " " + s; + + return s; +}; + + + +/** + * @class + * Represents a POSIX-style locale with its numeric, monetary and date/time + * properties. Also provides a set of locale helper methods. + * + *

The locale properties follow the POSIX standards: + * + *

+ * + * @public + * @constructor + * @description Creates a new locale object (POSIX-style) with the specified + * properties. + * + * @param {object} properties An object containing the raw locale properties: + * + * @param {String} properties.decimal_point + * + * A string containing the symbol that shall be used as the decimal + * delimiter (radix character) in numeric, non-monetary formatted + * quantities. This property cannot be omitted and cannot be set to the + * empty string. + * + * + * @param {String} properties.thousands_sep + * + * A string containing the symbol that shall be used as a separator for + * groups of digits to the left of the decimal delimiter in numeric, + * non-monetary formatted monetary quantities. + * + * + * @param {String} properties.grouping + * + * Defines the size of each group of digits in formatted non-monetary + * quantities. The operand is a sequence of integers separated by + * semicolons. Each integer specifies the number of digits in each group, + * with the initial integer defining the size of the group immediately + * preceding the decimal delimiter, and the following integers defining + * the preceding groups. If the last integer is not -1, then the size of + * the previous group (if any) shall be repeatedly used for the + * remainder of the digits. If the last integer is -1, then no further + * grouping shall be performed. + * + * + * @param {String} properties.int_curr_symbol + * + * The first three letters signify the ISO-4217 currency code, + * the fourth letter is the international symbol separation character + * (normally a space). + * + * + * @param {String} properties.currency_symbol + * + * The local shorthand currency symbol, e.g. "$" for the en_US locale + * + * + * @param {String} properties.mon_decimal_point + * + * The symbol to be used as the decimal delimiter (radix character) + * + * + * @param {String} properties.mon_thousands_sep + * + * The symbol to be used as a separator for groups of digits to the + * left of the decimal delimiter. + * + * + * @param {String} properties.mon_grouping + * + * A string that defines the size of each group of digits. The + * operand is a sequence of integers separated by semicolons (";"). + * Each integer specifies the number of digits in each group, with the + * initial integer defining the size of the group preceding the + * decimal delimiter, and the following integers defining the + * preceding groups. If the last integer is not -1, then the size of + * the previous group (if any) must be repeatedly used for the + * remainder of the digits. If the last integer is -1, then no + * further grouping is to be performed. + * + * + * @param {String} properties.positive_sign + * + * The string to indicate a non-negative monetary amount. + * + * + * @param {String} properties.negative_sign + * + * The string to indicate a negative monetary amount. + * + * + * @param {integer Number} properties.frac_digits + * + * An integer representing the number of fractional digits (those to + * the right of the decimal delimiter) to be written in a formatted + * monetary quantity using currency_symbol. + * + * + * @param {integer Number} properties.int_frac_digits + * + * An integer representing the number of fractional digits (those to + * the right of the decimal delimiter) to be written in a formatted + * monetary quantity using int_curr_symbol. + * + * + * @param {integer Number} properties.p_cs_precedes + * + * An integer set to 1 if the currency_symbol precedes the value for a + * monetary quantity with a non-negative value, and set to 0 if the + * symbol succeeds the value. + * + * + * @param {integer Number} properties.n_cs_precedes + * + * An integer set to 1 if the currency_symbol precedes the value for a + * monetary quantity with a negative value, and set to 0 if the symbol + * succeeds the value. + * + * + * @param {integer Number} properties.p_sep_by_space + * + * Set to a value indicating the separation of the currency_symbol, + * the sign string, and the value for a non-negative formatted monetary + * quantity: + * + *

0 No space separates the currency symbol and value.

+ * + *

1 If the currency symbol and sign string are adjacent, a space + * separates them from the value; otherwise, a space separates + * the currency symbol from the value.

+ * + *

2 If the currency symbol and sign string are adjacent, a space + * separates them; otherwise, a space separates the sign string + * from the value.

+ * + * + * @param {integer Number} properties.n_sep_by_space + * + * Set to a value indicating the separation of the currency_symbol, + * the sign string, and the value for a negative formatted monetary + * quantity. Rules same as for p_sep_by_space. + * + * + * @param {integer Number} properties.p_sign_posn + * + * An integer set to a value indicating the positioning of the + * positive_sign for a monetary quantity with a non-negative value: + * + *

0 Parentheses enclose the quantity and the currency_symbol.

+ * + *

1 The sign string precedes the quantity and the currency_symbol.

+ * + *

2 The sign string succeeds the quantity and the currency_symbol.

+ * + *

3 The sign string precedes the currency_symbol.

+ * + *

4 The sign string succeeds the currency_symbol.

+ * + * + * @param {integer Number} properties.n_sign_posn + * + * An integer set to a value indicating the positioning of the + * negative_sign for a negative formatted monetary quantity. Rules same + * as for p_sign_posn. + * + * + * @param {integer Number} properties.int_p_cs_precedes + * + * An integer set to 1 if the int_curr_symbol precedes the value for a + * monetary quantity with a non-negative value, and set to 0 if the + * symbol succeeds the value. + * + * + * @param {integer Number} properties.int_n_cs_precedes + * + * An integer set to 1 if the int_curr_symbol precedes the value for a + * monetary quantity with a negative value, and set to 0 if the symbol + * succeeds the value. + * + * + * @param {integer Number} properties.int_p_sep_by_space + * + * Set to a value indicating the separation of the int_curr_symbol, + * the sign string, and the value for a non-negative internationally + * formatted monetary quantity. Rules same as for p_sep_by_space. + * + * + * @param {integer Number} properties.int_n_sep_by_space + * + * Set to a value indicating the separation of the int_curr_symbol, + * the sign string, and the value for a negative internationally + * formatted monetary quantity. Rules same as for p_sep_by_space. + * + * + * @param {integer Number} properties.int_p_sign_posn + * + * An integer set to a value indicating the positioning of the + * positive_sign for a positive monetary quantity formatted with the + * international format. Rules same as for p_sign_posn. + * + * + * @param {integer Number} properties.int_n_sign_posn + * + * An integer set to a value indicating the positioning of the + * negative_sign for a negative monetary quantity formatted with the + * international format. Rules same as for p_sign_posn. + * + * + * @param {String[] | String} properties.abday + * + * The abbreviated weekday names, corresponding to the %a conversion + * specification. The property must be either an array of 7 strings or + * a string consisting of 7 semicolon-separated substrings, each + * surrounded by double-quotes. The first must be the abbreviated name + * of the day corresponding to Sunday, the second the abbreviated name + * of the day corresponding to Monday, and so on. + * + * + * @param {String[] | String} properties.day + * + * The full weekday names, corresponding to the %A conversion + * specification. The property must be either an array of 7 strings or + * a string consisting of 7 semicolon-separated substrings, each + * surrounded by double-quotes. The first must be the full name of the + * day corresponding to Sunday, the second the full name of the day + * corresponding to Monday, and so on. + * + * + * @param {String[] | String} properties.abmon + * + * The abbreviated month names, corresponding to the %b conversion + * specification. The property must be either an array of 12 strings or + * a string consisting of 12 semicolon-separated substrings, each + * surrounded by double-quotes. The first must be the abbreviated name + * of the first month of the year (January), the second the abbreviated + * name of the second month, and so on. + * + * + * @param {String[] | String} properties.mon + * + * The full month names, corresponding to the %B conversion + * specification. The property must be either an array of 12 strings or + * a string consisting of 12 semicolon-separated substrings, each + * surrounded by double-quotes. The first must be the full name of the + * first month of the year (January), the second the full name of the second + * month, and so on. + * + * + * @param {String} properties.d_fmt + * + * The appropriate date representation. The string may contain any + * combination of characters and conversion specifications (%). + * + * + * @param {String} properties.t_fmt + * + * The appropriate time representation. The string may contain any + * combination of characters and conversion specifications (%). + * + * + * @param {String} properties.d_t_fmt + * + * The appropriate date and time representation. The string may contain + * any combination of characters and conversion specifications (%). + * + * + * @param {String[] | String} properties.am_pm + * + * The appropriate representation of the ante-meridiem and post-meridiem + * strings, corresponding to the %p conversion specification. The property + * must be either an array of 2 strings or a string consisting of 2 + * semicolon-separated substrings, each surrounded by double-quotes. + * The first string must represent the ante-meridiem designation, the + * last string the post-meridiem designation. + * + * + * @throws @throws Error on a undefined or invalid locale property. + */ +jsworld.Locale = function(properties) { + + + /** + * @private + * + * @description Identifies the class for internal library purposes. + */ + this._className = "jsworld.Locale"; + + + /** + * @private + * + * @description Parses a day or month name definition list, which + * could be a ready JS array, e.g. ["Mon", "Tue", "Wed"...] or + * it could be a string formatted according to the classic POSIX + * definition e.g. "Mon";"Tue";"Wed";... + * + * @param {String[] | String} namesAn array or string defining + * the week/month names. + * @param {integer Number} expectedItems The number of expected list + * items, e.g. 7 for weekdays, 12 for months. + * + * @returns {String[]} The parsed (and checked) items. + * + * @throws Error on missing definition, unexpected item count or + * missing double-quotes. + */ + this._parseList = function(names, expectedItems) { + + var array = []; + + if (names == null) { + throw "Names not defined"; + } + else if (typeof names == "object") { + // we got a ready array + array = names; + } + else if (typeof names == "string") { + // we got the names in the classic POSIX form, do parse + array = names.split(";", expectedItems); + + for (var i = 0; i < array.length; i++) { + // check for and strip double quotes + if (array[i][0] == "\"" && array[i][array[i].length - 1] == "\"") + array[i] = array[i].slice(1, -1); + else + throw "Missing double quotes"; + } + } + else { + throw "Names must be an array or a string"; + } + + if (array.length != expectedItems) + throw "Expected " + expectedItems + " items, got " + array.length; + + return array; + }; + + + /** + * @private + * + * @description Validates a date/time format string, such as "H:%M:%S". + * Checks that the argument is of type "string" and is not empty. + * + * @param {String} formatString The format string. + * + * @returns {String} The validated string. + * + * @throws Error on null or empty string. + */ + this._validateFormatString = function(formatString) { + + if (typeof formatString == "string" && formatString.length > 0) + return formatString; + else + throw "Empty or no string"; + }; + + + // LC_NUMERIC + + if (properties == null || typeof properties != "object") + throw "Error: Invalid/missing locale properties"; + + + if (typeof properties.decimal_point != "string") + throw "Error: Invalid/missing decimal_point property"; + + this.decimal_point = properties.decimal_point; + + + if (typeof properties.thousands_sep != "string") + throw "Error: Invalid/missing thousands_sep property"; + + this.thousands_sep = properties.thousands_sep; + + + if (typeof properties.grouping != "string") + throw "Error: Invalid/missing grouping property"; + + this.grouping = properties.grouping; + + + // LC_MONETARY + + if (typeof properties.int_curr_symbol != "string") + throw "Error: Invalid/missing int_curr_symbol property"; + + if (! /[A-Za-z]{3}.?/.test(properties.int_curr_symbol)) + throw "Error: Invalid int_curr_symbol property"; + + this.int_curr_symbol = properties.int_curr_symbol; + + + if (typeof properties.currency_symbol != "string") + throw "Error: Invalid/missing currency_symbol property"; + + this.currency_symbol = properties.currency_symbol; + + + if (typeof properties.frac_digits != "number" && properties.frac_digits < 0) + throw "Error: Invalid/missing frac_digits property"; + + this.frac_digits = properties.frac_digits; + + + // may be empty string/null for currencies with no fractional part + if (properties.mon_decimal_point === null || properties.mon_decimal_point == "") { + + if (this.frac_digits > 0) + throw "Error: Undefined mon_decimal_point property"; + else + properties.mon_decimal_point = ""; + } + + if (typeof properties.mon_decimal_point != "string") + throw "Error: Invalid/missing mon_decimal_point property"; + + this.mon_decimal_point = properties.mon_decimal_point; + + + if (typeof properties.mon_thousands_sep != "string") + throw "Error: Invalid/missing mon_thousands_sep property"; + + this.mon_thousands_sep = properties.mon_thousands_sep; + + + if (typeof properties.mon_grouping != "string") + throw "Error: Invalid/missing mon_grouping property"; + + this.mon_grouping = properties.mon_grouping; + + + if (typeof properties.positive_sign != "string") + throw "Error: Invalid/missing positive_sign property"; + + this.positive_sign = properties.positive_sign; + + + if (typeof properties.negative_sign != "string") + throw "Error: Invalid/missing negative_sign property"; + + this.negative_sign = properties.negative_sign; + + + + if (properties.p_cs_precedes !== 0 && properties.p_cs_precedes !== 1) + throw "Error: Invalid/missing p_cs_precedes property, must be 0 or 1"; + + this.p_cs_precedes = properties.p_cs_precedes; + + + if (properties.n_cs_precedes !== 0 && properties.n_cs_precedes !== 1) + throw "Error: Invalid/missing n_cs_precedes, must be 0 or 1"; + + this.n_cs_precedes = properties.n_cs_precedes; + + + if (properties.p_sep_by_space !== 0 && + properties.p_sep_by_space !== 1 && + properties.p_sep_by_space !== 2) + throw "Error: Invalid/missing p_sep_by_space property, must be 0, 1 or 2"; + + this.p_sep_by_space = properties.p_sep_by_space; + + + if (properties.n_sep_by_space !== 0 && + properties.n_sep_by_space !== 1 && + properties.n_sep_by_space !== 2) + throw "Error: Invalid/missing n_sep_by_space property, must be 0, 1, or 2"; + + this.n_sep_by_space = properties.n_sep_by_space; + + + if (properties.p_sign_posn !== 0 && + properties.p_sign_posn !== 1 && + properties.p_sign_posn !== 2 && + properties.p_sign_posn !== 3 && + properties.p_sign_posn !== 4) + throw "Error: Invalid/missing p_sign_posn property, must be 0, 1, 2, 3 or 4"; + + this.p_sign_posn = properties.p_sign_posn; + + + if (properties.n_sign_posn !== 0 && + properties.n_sign_posn !== 1 && + properties.n_sign_posn !== 2 && + properties.n_sign_posn !== 3 && + properties.n_sign_posn !== 4) + throw "Error: Invalid/missing n_sign_posn property, must be 0, 1, 2, 3 or 4"; + + this.n_sign_posn = properties.n_sign_posn; + + + if (typeof properties.int_frac_digits != "number" && properties.int_frac_digits < 0) + throw "Error: Invalid/missing int_frac_digits property"; + + this.int_frac_digits = properties.int_frac_digits; + + + if (properties.int_p_cs_precedes !== 0 && properties.int_p_cs_precedes !== 1) + throw "Error: Invalid/missing int_p_cs_precedes property, must be 0 or 1"; + + this.int_p_cs_precedes = properties.int_p_cs_precedes; + + + if (properties.int_n_cs_precedes !== 0 && properties.int_n_cs_precedes !== 1) + throw "Error: Invalid/missing int_n_cs_precedes property, must be 0 or 1"; + + this.int_n_cs_precedes = properties.int_n_cs_precedes; + + + if (properties.int_p_sep_by_space !== 0 && + properties.int_p_sep_by_space !== 1 && + properties.int_p_sep_by_space !== 2) + throw "Error: Invalid/missing int_p_sep_by_spacev, must be 0, 1 or 2"; + + this.int_p_sep_by_space = properties.int_p_sep_by_space; + + + if (properties.int_n_sep_by_space !== 0 && + properties.int_n_sep_by_space !== 1 && + properties.int_n_sep_by_space !== 2) + throw "Error: Invalid/missing int_n_sep_by_space property, must be 0, 1, or 2"; + + this.int_n_sep_by_space = properties.int_n_sep_by_space; + + + if (properties.int_p_sign_posn !== 0 && + properties.int_p_sign_posn !== 1 && + properties.int_p_sign_posn !== 2 && + properties.int_p_sign_posn !== 3 && + properties.int_p_sign_posn !== 4) + throw "Error: Invalid/missing int_p_sign_posn property, must be 0, 1, 2, 3 or 4"; + + this.int_p_sign_posn = properties.int_p_sign_posn; + + + if (properties.int_n_sign_posn !== 0 && + properties.int_n_sign_posn !== 1 && + properties.int_n_sign_posn !== 2 && + properties.int_n_sign_posn !== 3 && + properties.int_n_sign_posn !== 4) + throw "Error: Invalid/missing int_n_sign_posn property, must be 0, 1, 2, 3 or 4"; + + this.int_n_sign_posn = properties.int_n_sign_posn; + + + // LC_TIME + + if (properties == null || typeof properties != "object") + throw "Error: Invalid/missing time locale properties"; + + + // parse the supported POSIX LC_TIME properties + + // abday + try { + this.abday = this._parseList(properties.abday, 7); + } + catch (error) { + throw "Error: Invalid abday property: " + error; + } + + // day + try { + this.day = this._parseList(properties.day, 7); + } + catch (error) { + throw "Error: Invalid day property: " + error; + } + + // abmon + try { + this.abmon = this._parseList(properties.abmon, 12); + } catch (error) { + throw "Error: Invalid abmon property: " + error; + } + + // mon + try { + this.mon = this._parseList(properties.mon, 12); + } catch (error) { + throw "Error: Invalid mon property: " + error; + } + + // d_fmt + try { + this.d_fmt = this._validateFormatString(properties.d_fmt); + } catch (error) { + throw "Error: Invalid d_fmt property: " + error; + } + + // t_fmt + try { + this.t_fmt = this._validateFormatString(properties.t_fmt); + } catch (error) { + throw "Error: Invalid t_fmt property: " + error; + } + + // d_t_fmt + try { + this.d_t_fmt = this._validateFormatString(properties.d_t_fmt); + } catch (error) { + throw "Error: Invalid d_t_fmt property: " + error; + } + + // am_pm + try { + var am_pm_strings = this._parseList(properties.am_pm, 2); + this.am = am_pm_strings[0]; + this.pm = am_pm_strings[1]; + } catch (error) { + // ignore empty/null string errors + this.am = ""; + this.pm = ""; + } + + + /** + * @public + * + * @description Returns the abbreviated name of the specified weekday. + * + * @param {integer Number} [weekdayNum] An integer between 0 and 6. Zero + * corresponds to Sunday, one to Monday, etc. If omitted the + * method will return an array of all abbreviated weekday + * names. + * + * @returns {String | String[]} The abbreviated name of the specified weekday + * or an array of all abbreviated weekday names. + * + * @throws Error on invalid argument. + */ + this.getAbbreviatedWeekdayName = function(weekdayNum) { + + if (typeof weekdayNum == "undefined" || weekdayNum === null) + return this.abday; + + if (! jsworld._isInteger(weekdayNum) || weekdayNum < 0 || weekdayNum > 6) + throw "Error: Invalid weekday argument, must be an integer [0..6]"; + + return this.abday[weekdayNum]; + }; + + + /** + * @public + * + * @description Returns the name of the specified weekday. + * + * @param {integer Number} [weekdayNum] An integer between 0 and 6. Zero + * corresponds to Sunday, one to Monday, etc. If omitted the + * method will return an array of all weekday names. + * + * @returns {String | String[]} The name of the specified weekday or an + * array of all weekday names. + * + * @throws Error on invalid argument. + */ + this.getWeekdayName = function(weekdayNum) { + + if (typeof weekdayNum == "undefined" || weekdayNum === null) + return this.day; + + if (! jsworld._isInteger(weekdayNum) || weekdayNum < 0 || weekdayNum > 6) + throw "Error: Invalid weekday argument, must be an integer [0..6]"; + + return this.day[weekdayNum]; + }; + + + /** + * @public + * + * @description Returns the abbreviated name of the specified month. + * + * @param {integer Number} [monthNum] An integer between 0 and 11. Zero + * corresponds to January, one to February, etc. If omitted the + * method will return an array of all abbreviated month names. + * + * @returns {String | String[]} The abbreviated name of the specified month + * or an array of all abbreviated month names. + * + * @throws Error on invalid argument. + */ + this.getAbbreviatedMonthName = function(monthNum) { + + if (typeof monthNum == "undefined" || monthNum === null) + return this.abmon; + + if (! jsworld._isInteger(monthNum) || monthNum < 0 || monthNum > 11) + throw "Error: Invalid month argument, must be an integer [0..11]"; + + return this.abmon[monthNum]; + }; + + + /** + * @public + * + * @description Returns the name of the specified month. + * + * @param {integer Number} [monthNum] An integer between 0 and 11. Zero + * corresponds to January, one to February, etc. If omitted the + * method will return an array of all month names. + * + * @returns {String | String[]} The name of the specified month or an array + * of all month names. + * + * @throws Error on invalid argument. + */ + this.getMonthName = function(monthNum) { + + if (typeof monthNum == "undefined" || monthNum === null) + return this.mon; + + if (! jsworld._isInteger(monthNum) || monthNum < 0 || monthNum > 11) + throw "Error: Invalid month argument, must be an integer [0..11]"; + + return this.mon[monthNum]; + }; + + + + /** + * @public + * + * @description Gets the decimal delimiter (radix) character for + * numeric quantities. + * + * @returns {String} The radix character. + */ + this.getDecimalPoint = function() { + + return this.decimal_point; + }; + + + /** + * @public + * + * @description Gets the local shorthand currency symbol. + * + * @returns {String} The currency symbol. + */ + this.getCurrencySymbol = function() { + + return this.currency_symbol; + }; + + + /** + * @public + * + * @description Gets the internaltion currency symbol (ISO-4217 code). + * + * @returns {String} The international currency symbol. + */ + this.getIntCurrencySymbol = function() { + + return this.int_curr_symbol.substring(0,3); + }; + + + /** + * @public + * + * @description Gets the position of the local (shorthand) currency + * symbol relative to the amount. Assumes a non-negative amount. + * + * @returns {Boolean} True if the symbol precedes the amount, false if + * the symbol succeeds the amount. + */ + this.currencySymbolPrecedes = function() { + + if (this.p_cs_precedes == 1) + return true; + else + return false; + }; + + + /** + * @public + * + * @description Gets the position of the international (ISO-4217 code) + * currency symbol relative to the amount. Assumes a non-negative + * amount. + * + * @returns {Boolean} True if the symbol precedes the amount, false if + * the symbol succeeds the amount. + */ + this.intCurrencySymbolPrecedes = function() { + + if (this.int_p_cs_precedes == 1) + return true; + else + return false; + + }; + + + /** + * @public + * + * @description Gets the decimal delimiter (radix) for monetary + * quantities. + * + * @returns {String} The radix character. + */ + this.getMonetaryDecimalPoint = function() { + + return this.mon_decimal_point; + }; + + + /** + * @public + * + * @description Gets the number of fractional digits for local + * (shorthand) symbol formatting. + * + * @returns {integer Number} The number of fractional digits. + */ + this.getFractionalDigits = function() { + + return this.frac_digits; + }; + + + /** + * @public + * + * @description Gets the number of fractional digits for + * international (ISO-4217 code) formatting. + * + * @returns {integer Number} The number of fractional digits. + */ + this.getIntFractionalDigits = function() { + + return this.int_frac_digits; + }; +}; + + + +/** + * @class + * Class for localised formatting of numbers. + * + *

See: + * POSIX LC_NUMERIC. + * + * + * @public + * @constructor + * @description Creates a new numeric formatter for the specified locale. + * + * @param {jsworld.Locale} locale A locale object specifying the required + * POSIX LC_NUMERIC formatting properties. + * + * @throws Error on constructor failure. + */ +jsworld.NumericFormatter = function(locale) { + + if (typeof locale != "object" || locale._className != "jsworld.Locale") + throw "Constructor error: You must provide a valid jsworld.Locale instance"; + + this.lc = locale; + + + /** + * @public + * + * @description Formats a decimal numeric value according to the preset + * locale. + * + * @param {Number|String} number The number to format. + * @param {String} [options] Options to modify the formatted output: + *

    + *
  • "^" suppress grouping + *
  • "+" force positive sign for positive amounts + *
  • "~" suppress positive/negative sign + *
  • ".n" specify decimal precision 'n' + *
+ * + * @returns {String} The formatted number. + * + * @throws "Error: Invalid input" on bad input. + */ + this.format = function(number, options) { + + if (typeof number == "string") + number = jsworld._trim(number); + + if (! jsworld._isNumber(number)) + throw "Error: The input is not a number"; + + var floatAmount = parseFloat(number, 10); + + // get the required precision + var reqPrecision = jsworld._getPrecision(options); + + // round to required precision + if (reqPrecision != -1) + floatAmount = Math.round(floatAmount * Math.pow(10, reqPrecision)) / Math.pow(10, reqPrecision); + + + // convert the float number to string and parse into + // object with properties integer and fraction + var parsedAmount = jsworld._splitNumber(String(floatAmount)); + + // format integer part with grouping chars + var formattedIntegerPart; + + if (floatAmount === 0) + formattedIntegerPart = "0"; + else + formattedIntegerPart = jsworld._hasOption("^", options) ? + parsedAmount.integer : + jsworld._formatIntegerPart(parsedAmount.integer, + this.lc.grouping, + this.lc.thousands_sep); + + // format the fractional part + var formattedFractionPart = + reqPrecision != -1 ? + jsworld._formatFractionPart(parsedAmount.fraction, reqPrecision) : + parsedAmount.fraction; + + + // join the integer and fraction parts using the decimal_point property + var formattedAmount = + formattedFractionPart.length ? + formattedIntegerPart + this.lc.decimal_point + formattedFractionPart : + formattedIntegerPart; + + // prepend sign? + if (jsworld._hasOption("~", options) || floatAmount === 0) { + // suppress both '+' and '-' signs, i.e. return abs value + return formattedAmount; + } + else { + if (jsworld._hasOption("+", options) || floatAmount < 0) { + if (floatAmount > 0) + // force '+' sign for positive amounts + return "+" + formattedAmount; + else if (floatAmount < 0) + // prepend '-' sign + return "-" + formattedAmount; + else + // zero case + return formattedAmount; + } + else { + // positive amount with no '+' sign + return formattedAmount; + } + } + }; +}; + + +/** + * @class + * Class for localised formatting of dates and times. + * + *

See: + * POSIX LC_TIME. + * + * @public + * @constructor + * @description Creates a new date/time formatter for the specified locale. + * + * @param {jsworld.Locale} locale A locale object specifying the required + * POSIX LC_TIME formatting properties. + * + * @throws Error on constructor failure. + */ +jsworld.DateTimeFormatter = function(locale) { + + + if (typeof locale != "object" || locale._className != "jsworld.Locale") + throw "Constructor error: You must provide a valid jsworld.Locale instance."; + + this.lc = locale; + + + /** + * @public + * + * @description Formats a date according to the preset locale. + * + * @param {Date|String} date A valid Date object instance or a string + * containing a valid ISO-8601 formatted date, e.g. "2010-31-03" + * or "2010-03-31 23:59:59". + * + * @returns {String} The formatted date + * + * @throws Error on invalid date argument + */ + this.formatDate = function(date) { + + var d = null; + + if (typeof date == "string") { + // assume ISO-8601 date string + try { + d = jsworld.parseIsoDate(date); + } catch (error) { + // try full ISO-8601 date/time string + d = jsworld.parseIsoDateTime(date); + } + } + else if (date !== null && typeof date == "object") { + // assume ready Date object + d = date; + } + else { + throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string"; + } + + return this._applyFormatting(d, this.lc.d_fmt); + }; + + + /** + * @public + * + * @description Formats a time according to the preset locale. + * + * @param {Date|String} date A valid Date object instance or a string + * containing a valid ISO-8601 formatted time, e.g. "23:59:59" + * or "2010-03-31 23:59:59". + * + * @returns {String} The formatted time. + * + * @throws Error on invalid date argument. + */ + this.formatTime = function(date) { + + var d = null; + + if (typeof date == "string") { + // assume ISO-8601 time string + try { + d = jsworld.parseIsoTime(date); + } catch (error) { + // try full ISO-8601 date/time string + d = jsworld.parseIsoDateTime(date); + } + } + else if (date !== null && typeof date == "object") { + // assume ready Date object + d = date; + } + else { + throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string"; + } + + return this._applyFormatting(d, this.lc.t_fmt); + }; + + + /** + * @public + * + * @description Formats a date/time value according to the preset + * locale. + * + * @param {Date|String} date A valid Date object instance or a string + * containing a valid ISO-8601 formatted date/time, e.g. + * "2010-03-31 23:59:59". + * + * @returns {String} The formatted time. + * + * @throws Error on invalid argument. + */ + this.formatDateTime = function(date) { + + var d = null; + + if (typeof date == "string") { + // assume ISO-8601 format + d = jsworld.parseIsoDateTime(date); + } + else if (date !== null && typeof date == "object") { + // assume ready Date object + d = date; + } + else { + throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string"; + } + + return this._applyFormatting(d, this.lc.d_t_fmt); + }; + + + /** + * @private + * + * @description Apples formatting to the Date object according to the + * format string. + * + * @param {Date} d A valid Date instance. + * @param {String} s The formatting string with '%' placeholders. + * + * @returns {String} The formatted string. + */ + this._applyFormatting = function(d, s) { + + s = s.replace(/%%/g, '%'); + s = s.replace(/%a/g, this.lc.abday[d.getDay()]); + s = s.replace(/%A/g, this.lc.day[d.getDay()]); + s = s.replace(/%b/g, this.lc.abmon[d.getMonth()]); + s = s.replace(/%B/g, this.lc.mon[d.getMonth()]); + s = s.replace(/%d/g, jsworld._zeroPad(d.getDate(), 2)); + s = s.replace(/%e/g, jsworld._spacePad(d.getDate(), 2)); + s = s.replace(/%F/g, d.getFullYear() + + "-" + + jsworld._zeroPad(d.getMonth()+1, 2) + + "-" + + jsworld._zeroPad(d.getDate(), 2)); + s = s.replace(/%h/g, this.lc.abmon[d.getMonth()]); // same as %b + s = s.replace(/%H/g, jsworld._zeroPad(d.getHours(), 2)); + s = s.replace(/%I/g, jsworld._zeroPad(this._hours12(d.getHours()), 2)); + s = s.replace(/%k/g, d.getHours()); + s = s.replace(/%l/g, this._hours12(d.getHours())); + s = s.replace(/%m/g, jsworld._zeroPad(d.getMonth()+1, 2)); + s = s.replace(/%n/g, "\n"); + s = s.replace(/%M/g, jsworld._zeroPad(d.getMinutes(), 2)); + s = s.replace(/%p/g, this._getAmPm(d.getHours())); + s = s.replace(/%P/g, this._getAmPm(d.getHours()).toLocaleLowerCase()); // safe? + s = s.replace(/%R/g, jsworld._zeroPad(d.getHours(), 2) + + ":" + + jsworld._zeroPad(d.getMinutes(), 2)); + s = s.replace(/%S/g, jsworld._zeroPad(d.getSeconds(), 2)); + s = s.replace(/%T/g, jsworld._zeroPad(d.getHours(), 2) + + ":" + + jsworld._zeroPad(d.getMinutes(), 2) + + ":" + + jsworld._zeroPad(d.getSeconds(), 2)); + s = s.replace(/%w/g, this.lc.day[d.getDay()]); + s = s.replace(/%y/g, new String(d.getFullYear()).substring(2)); + s = s.replace(/%Y/g, d.getFullYear()); + + s = s.replace(/%Z/g, ""); // to do: ignored until a reliable TMZ method found + + s = s.replace(/%[a-zA-Z]/g, ""); // ignore all other % sequences + + return s; + }; + + + /** + * @private + * + * @description Does 24 to 12 hour conversion. + * + * @param {integer Number} hour24 Hour [0..23]. + * + * @returns {integer Number} Corresponding hour [1..12]. + */ + this._hours12 = function(hour24) { + + if (hour24 === 0) + return 12; // 00h is 12AM + + else if (hour24 > 12) + return hour24 - 12; // 1PM to 11PM + + else + return hour24; // 1AM to 12PM + }; + + + /** + * @private + * + * @description Gets the appropriate localised AM or PM string depending + * on the day hour. Special cases: midnight is 12AM, noon is 12PM. + * + * @param {integer Number} hour24 Hour [0..23]. + * + * @returns {String} The corresponding localised AM or PM string. + */ + this._getAmPm = function(hour24) { + + if (hour24 < 12) + return this.lc.am; + else + return this.lc.pm; + }; +}; + + + +/** + * @class Class for localised formatting of currency amounts. + * + *

See: + * POSIX LC_MONETARY. + * + * @public + * @constructor + * @description Creates a new monetary formatter for the specified locale. + * + * @param {jsworld.Locale} locale A locale object specifying the required + * POSIX LC_MONETARY formatting properties. + * @param {String} [currencyCode] Set the currency explicitly by + * passing its international ISO-4217 code, e.g. "USD", "EUR", "GBP". + * Use this optional parameter to override the default local currency + * @param {String} [altIntSymbol] Non-local currencies are formatted + * with their international ISO-4217 code to prevent ambiguity. + * Use this optional argument to force a different symbol, such as the + * currency's shorthand sign. This is mostly useful when the shorthand + * sign is both internationally recognised and identifies the currency + * uniquely (e.g. the Euro sign). + * + * @throws Error on constructor failure. + */ +jsworld.MonetaryFormatter = function(locale, currencyCode, altIntSymbol) { + + if (typeof locale != "object" || locale._className != "jsworld.Locale") + throw "Constructor error: You must provide a valid jsworld.Locale instance"; + + this.lc = locale; + + /** + * @private + * @description Lookup table to determine the fraction digits for a + * specific currency; most currencies subdivide at 1/100 (2 fractional + * digits), so we store only those that deviate from the default. + * + *

The data is from Unicode's CLDR version 1.7.0. The two currencies + * with non-decimal subunits (MGA and MRO) are marked as having no + * fractional digits as well as all currencies that have no subunits + * in circulation. + * + *

It is "hard-wired" for referential convenience and is only looked + * up when an overriding currencyCode parameter is supplied. + */ + this.currencyFractionDigits = { + "AFN" : 0, "ALL" : 0, "AMD" : 0, "BHD" : 3, "BIF" : 0, + "BYR" : 0, "CLF" : 0, "CLP" : 0, "COP" : 0, "CRC" : 0, + "DJF" : 0, "GNF" : 0, "GYD" : 0, "HUF" : 0, "IDR" : 0, + "IQD" : 0, "IRR" : 0, "ISK" : 0, "JOD" : 3, "JPY" : 0, + "KMF" : 0, "KRW" : 0, "KWD" : 3, "LAK" : 0, "LBP" : 0, + "LYD" : 3, "MGA" : 0, "MMK" : 0, "MNT" : 0, "MRO" : 0, + "MUR" : 0, "OMR" : 3, "PKR" : 0, "PYG" : 0, "RSD" : 0, + "RWF" : 0, "SLL" : 0, "SOS" : 0, "STD" : 0, "SYP" : 0, + "TND" : 3, "TWD" : 0, "TZS" : 0, "UGX" : 0, "UZS" : 0, + "VND" : 0, "VUV" : 0, "XAF" : 0, "XOF" : 0, "XPF" : 0, + "YER" : 0, "ZMK" : 0 + }; + + + // optional currencyCode argument? + if (typeof currencyCode == "string") { + // user wanted to override the local currency + this.currencyCode = currencyCode.toUpperCase(); + + // must override the frac digits too, for some + // currencies have 0, 2 or 3! + var numDigits = this.currencyFractionDigits[this.currencyCode]; + if (typeof numDigits != "number") + numDigits = 2; // default for most currencies + this.lc.frac_digits = numDigits; + this.lc.int_frac_digits = numDigits; + } + else { + // use local currency + this.currencyCode = this.lc.int_curr_symbol.substring(0,3).toUpperCase(); + } + + // extract intl. currency separator + this.intSep = this.lc.int_curr_symbol.charAt(3); + + // flag local or intl. sign formatting? + if (this.currencyCode == this.lc.int_curr_symbol.substring(0,3)) { + // currency matches the local one? -> + // formatting with local symbol and parameters + this.internationalFormatting = false; + this.curSym = this.lc.currency_symbol; + } + else { + // currency doesn't match the local -> + + // do we have an overriding currency symbol? + if (typeof altIntSymbol == "string") { + // -> force formatting with local parameters, using alt symbol + this.curSym = altIntSymbol; + this.internationalFormatting = false; + } + else { + // -> force formatting with intl. sign and parameters + this.internationalFormatting = true; + } + } + + + /** + * @public + * + * @description Gets the currency symbol used in formatting. + * + * @returns {String} The currency symbol. + */ + this.getCurrencySymbol = function() { + + return this.curSym; + }; + + + /** + * @public + * + * @description Gets the position of the currency symbol relative to + * the amount. Assumes a non-negative amount and local formatting. + * + * @param {String} intFlag Optional flag to force international + * formatting by passing the string "i". + * + * @returns {Boolean} True if the symbol precedes the amount, false if + * the symbol succeeds the amount. + */ + this.currencySymbolPrecedes = function(intFlag) { + + if (typeof intFlag == "string" && intFlag == "i") { + // international formatting was forced + if (this.lc.int_p_cs_precedes == 1) + return true; + else + return false; + + } + else { + // check whether local formatting is on or off + if (this.internationalFormatting) { + if (this.lc.int_p_cs_precedes == 1) + return true; + else + return false; + } + else { + if (this.lc.p_cs_precedes == 1) + return true; + else + return false; + } + } + }; + + + /** + * @public + * + * @description Gets the decimal delimiter (radix) used in formatting. + * + * @returns {String} The radix character. + */ + this.getDecimalPoint = function() { + + return this.lc.mon_decimal_point; + }; + + + /** + * @public + * + * @description Gets the number of fractional digits. Assumes local + * formatting. + * + * @param {String} intFlag Optional flag to force international + * formatting by passing the string "i". + * + * @returns {integer Number} The number of fractional digits. + */ + this.getFractionalDigits = function(intFlag) { + + if (typeof intFlag == "string" && intFlag == "i") { + // international formatting was forced + return this.lc.int_frac_digits; + } + else { + // check whether local formatting is on or off + if (this.internationalFormatting) + return this.lc.int_frac_digits; + else + return this.lc.frac_digits; + } + }; + + + /** + * @public + * + * @description Formats a monetary amount according to the preset + * locale. + * + *

+	 * For local currencies the native shorthand symbol will be used for
+	 * formatting.
+	 * Example:
+	 *        locale is en_US
+	 *        currency is USD
+	 *        -> the "$" symbol will be used, e.g. $123.45
+	 *        
+	 * For non-local currencies the international ISO-4217 code will be
+	 * used for formatting.
+	 * Example:
+	 *       locale is en_US (which has USD as currency)
+	 *       currency is EUR
+	 *       -> the ISO three-letter code will be used, e.g. EUR 123.45
+	 *
+	 * If the currency is non-local, but an alternative currency symbol was
+	 * provided, this will be used instead.
+	 * Example
+	 *       locale is en_US (which has USD as currency)
+	 *       currency is EUR
+	 *       an alternative symbol is provided - "€"
+	 *       -> the alternative symbol will be used, e.g. €123.45
+	 * 
+ * + * @param {Number|String} amount The amount to format as currency. + * @param {String} [options] Options to modify the formatted output: + *
'; +html += '
Post Game Log Message
'; +html += '
'; +html += ''; +html += '
Enter your log message here. Plain text only please.
'; +html += '
'; +html += '

'; +html += ''; +html += ''; +html += ''; +html += '
' + large_icon_button('x', 'Cancel', "hide_popup_dialog()") + ' ' + large_icon_button('check', 'Post Message', "glog_post('"+game_id+"')") + '
'; +html += '
'; +html += ''; +session.hooks.keys[ESC_KEY] = 'hide_popup_dialog'; +safe_focus( 'fe_glog_body' ); +show_popup_dialog(500, 175, html); +} +function glog_post(game_id) { +var msg = trim( $('fe_glog_body').value ); +if (msg) { +hide_popup_dialog(); +effect_api_send('game_post_log', { +GameID: game_id, +Message: msg +}, [this, 'glog_post_finish'], { _game_id: game_id }); +} +} +function glog_post_finish(response, tx) { +show_glog_widget( tx._game_id ); +} +function hide_glog_widget() { +$('glog_widget').hide(); +} +function get_icon_for_glog_type(type) { +var icon = 'page_white.png'; +switch (type) { +case 'asset': icon = 'folder_page_white.png'; break; +case 'game': icon = 'controller.png'; break; +case 'member': icon = 'user'; break; +case 'comment': icon = 'comment.png'; break; +case 'level': icon = 'world.png'; break; +case 'sprite': icon = 'cog.png'; break; +case 'tile': icon = 'brick.png'; break; +case 'tileset': icon = 'color_swatch.png'; break; +case 'rev': icon = 'cd.png'; break; +case 'revision': icon = 'cd.png'; break; +case 'font': icon = 'style.png'; break; +case 'key': icon = 'keyboard.png'; break; +case 'audio': icon = 'sound'; break; +case 'payment': icon = 'money.png'; break; +case 'env': icon = 'weather.png'; break; +case 'environment': icon = 'weather.png'; break; +} +return icon; +} +function effect_load_script(url) { +Debug.trace('api', 'Loading script: ' + url); +load_script(url); +} +function effect_api_get_ie(cmd, params, userData) { +if (!session.api_state_ie) session.api_state_ie = {}; +var unique_id = get_unique_id(); +session.api_state_ie[unique_id] = userData; +params.format = 'js'; +params.onafter = 'effect_api_response_ie(' + unique_id + ', response);'; +var url = '/effect/api/' + cmd + composeQueryString(params); +Debug.trace('api', "Sending MSIE HTTP GET: " + url); +load_script(url); +} +function effect_api_response_ie(unique_id, tree) { +Debug.trace('api', "Got response from MSIE HTTP GET"); +var tx = session.api_state_ie[unique_id]; +delete session.api_state_ie[unique_id]; +if (tree.Code == 'session') { +do_logout_2(); +return; +} +if (tree.Code == 'access') { +do_notice("Access Denied", tree.Description, 'do_not_pass_go'); +return; +} +if (tree.Code != 0) { +if (tx._on_error) return fire_callback( tx._on_error, tree, tx ); +return do_error( tree.Description ); +} +if (tree.SessionID) { +if (tree.SessionID == '_DELETE_') { +delete session.cookie.tree.effect_session_id; +} +else { +session.cookie.set( 'effect_session_id', tree.SessionID ); +} +session.cookie.save(); +} +if (tx._api_callback) { +fire_callback( tx._api_callback, tree, tx ); +} +} +function effect_api_get(cmd, params, callback, userData) { +if (!userData) userData = {}; +userData._api_callback = callback; +if (!session.api_mod_cache[cmd] && session.username) session.api_mod_cache[cmd] = hires_time_now(); +if (!params.mod && session.api_mod_cache[cmd]) params.mod = session.api_mod_cache[cmd]; +if (ie) return effect_api_get_ie(cmd, params, userData); +var url = '/effect/api/' + cmd + composeQueryString(params); +Debug.trace('api', "Sending HTTP GET: " + url); +ajax.get( url, 'effect_api_response', userData ); +} +function effect_api_send(cmd, xml, callback, userData) { +if (!userData) userData = {}; +userData._api_callback = callback; +var data = compose_xml('EffectRequest', xml); +Debug.trace('api', "Sending API Command: " + cmd + ": " + data); +ajax.send({ +method: 'POST', +url: '/effect/api/' + cmd, +data: data, +headers: { 'Content-Type': 'text/xml' } +}, 'effect_api_response', userData); +} +function effect_api_response(tx) { +Debug.trace('api', "HTTP " + tx.response.code + ": " + tx.response.data); +if (tx.response.code == 999) { +if (tx.request._auto_retry) { +session.net_error = false; +show_progress_dialog(1, "Trying to reestablish connection..."); +session.net_error = true; +setTimeout( function() { ajax.send(tx.request); }, 1000 ); +return; +} +else return do_error( "HTTP ERROR: " + tx.response.code + ": " + tx.response.data + ' (URL: ' + tx.request.url + ')' ); +} +if (session.net_error) { +hide_progress_dialog(); +session.net_error = false; +} +if (tx.response.code != 200) { +if (tx._silent) return; +else return do_error( "HTTP ERROR: " + tx.response.code + ": " + tx.response.data + ' (URL: ' + tx.request.url + ')' ); +} +var tree = null; +if (!tx._raw) { +var parser = new XML({ +preserveAttributes: true, +text: tx.response.data +}); +if (parser.getLastError()) return do_error("XML PARSE ERROR: " + parser.getLastError()); +tree = parser.getTree(); +if (tree.Code == 'session') { +do_logout_2(); +return; +} +if (tree.Code == 'access') { +do_notice("Access Denied", tree.Description, 'do_not_pass_go'); +return; +} +if (tree.Code != 0) { +if (tx._on_error) return fire_callback( tx._on_error, tree, tx ); +return do_error( tree.Description ); +} +if (tree.SessionID) { +if (tree.SessionID == '_DELETE_') { +delete session.cookie.tree.effect_session_id; +} +else { +session.cookie.set( 'effect_session_id', tree.SessionID ); +} +session.cookie.save(); +} +} +if (tx._api_callback) { +fire_callback( tx._api_callback, tree, tx ); +} +} +function effect_api_mod_touch() { +for (var idx = 0, len = arguments.length; idx < len; idx++) { +session.api_mod_cache[ arguments[idx] ] = hires_time_now(); +} +} +function do_not_pass_go() { +Nav.go('Main'); +} +var Nav = { +loc: '', +old_loc: '', +inited: false, +nodes: [], +init: function() { +if (!this.inited) { +this.inited = true; +this.loc = 'init'; +this.monitor(); +} +}, +monitor: function() { +var parts = window.location.href.split(/\#/); +var anchor = parts[1]; +if (!anchor) anchor = 'Main'; +var full_anchor = '' + anchor; +var sub_anchor = ''; +anchor = anchor.replace(/\%7C/, '|'); +if (anchor.match(/\|(\w+)$/)) { +sub_anchor = RegExp.$1.toLowerCase(); +anchor = anchor.replace(/\|(\w+)$/, ''); +} +if ((anchor != this.loc) && !anchor.match(/^_/)) { +Debug.trace('nav', "Caught navigation anchor: " + full_anchor); +var page_name = ''; +var page_args = null; +if (full_anchor.match(/^\w+\?.+/)) { +parts = full_anchor.split(/\?/); +page_name = parts[0]; +page_args = parseQueryString( parts[1] ); +} +else if (full_anchor.match(/^(\w+)\/(.*)$/)) { +page_name = RegExp.$1; +page_args = RegExp.$2; +} +else { +parts = full_anchor.split(/\//); +page_name = parts[0]; +page_args = parts.slice(1); +} +Debug.trace('nav', "Calling page: " + page_name + ": " + serialize(page_args)); +hide_popup_dialog(); +var result = page_manager.click( page_name, page_args ); +if (result) { +if (window.pageTracker && (this.loc != 'init')) { +setTimeout( function() { pageTracker._trackPageview('/effect/' + anchor); }, 1000 ); +} +this.old_loc = this.loc; +if (this.old_loc == 'init') this.old_loc = 'Main'; +this.loc = anchor; +} +else { +this.go( this.loc ); +} +} +else if (sub_anchor != this.sub_anchor) { +Debug.trace('nav', "Caught sub-anchor: " + sub_anchor); +$P().gosub( sub_anchor ); +} +this.sub_anchor = sub_anchor; +setTimeout( 'Nav.monitor()', 100 ); +}, +go: function(anchor, force) { +anchor = anchor.replace(/^\#/, ''); +if (force) this.loc = 'init'; +window.location.href = '#' + anchor; +}, +prev: function() { +this.go( this.old_loc || 'Main' ); +}, +refresh: function() { +this.loc = 'refresh'; +}, +bar: function() { +var nodes = arguments; +var html = ''; +for (var idx = 0, len = nodes.length; idx < len; idx++) { +var node = nodes[idx]; +if (node) this.nodes[idx] = node; +else node = this.nodes[idx]; +if (node != '_ignore_') { +html += ''; +} +} +html += '
'; +$('d_nav_bar').innerHTML = html; +}, +title: function(name) { +if (name) document.title = name + ' | EffectGames.com'; +else document.title = 'EffectGames.com'; +}, +currentAnchor: function() { +var parts = window.location.href.split(/\#/); +var anchor = parts[1] || ''; +var sub_anchor = ''; +anchor = anchor.replace(/\%7C/, '|'); +if (anchor.match(/\|(\w+)$/)) { +sub_anchor = RegExp.$1.toLowerCase(); +anchor = anchor.replace(/\|(\w+)$/, ''); +} +return anchor; +} +}; +var Blog = { +edit_caption: '
*Bold*  |Italic|  {monospace}  [http://link]  Formatting Guide...
', +search: function(args) { +if (!args.mode) args.mode = 'and'; +if (!args.offset) args.offset = 0; +if (!args.limit) args.limit = 10; +if (!args.format) args.format = 'xml'; +var query_args = copy_object( args ); +delete query_args.callback; +effect_api_get( 'article_search', query_args, [this, 'search_response'], { _search_args: args } ); +}, +get_article_preview: function(row, args) { +var html = ''; +Debug.trace('blog', 'Row: ' + dumper(row)); +html += '
'; +var ext_article_url = 'http://' + location.hostname + '/effect/article.psp.html' + row.Path + '/' + row.ArticleID; +var article_url = '#Article' + row.Path + '/' + row.ArticleID; +html += ''; +if (!args.title_only) { +html += '
'; +html += row.Preview; +html += '  ' + (args.link_title || 'Read Full Story...') + ''; +html += '
'; +html += ''; +html += '
'; +var elem_class = args.footer_element_class || 'blog_preview_footer_element'; +if ((session.username == row.Username) || is_admin()) { +html += '
' + +icon('page_white_edit.png', "Edit", '#ArticleEdit?path=' + row.Path + '&id=' + row.ArticleID) + '
'; +} +html += '
' + get_user_display(row.Username) + '
'; +html += '
' + icon('calendar', get_short_date_time(row.Published)) + '
'; +html += '
' + icon('talk', row.Comments) + '
'; +if (0 && row.Tags) html += '
' + icon('note.png', make_tag_links(row.Tags, 3)) + '
'; +html += '
' + icon('facebook.png', 'Facebook', "window.open('http://www.facebook.com/sharer.php?u="+encodeURIComponent(ext_article_url)+'&t='+encodeURIComponent(row.Title)+"','sharer','toolbar=0,status=0,width=626,height=436')", "Share on Facebook") + '
'; +html += '
' + icon('twitter.png', 'Twitter', "window.open('http://twitter.com/home?status=Reading%20" + encodeURIComponent(row.Title) + "%3A%20" + encodeURIComponent(ext_article_url)+"')", "Share on Twitter") + '
'; +html += '
'; +html += '
'; +html += '
'; +} +html += '
'; +return html; +}, +search_response: function(response, tx) { +var args = tx._search_args; +if (args.callback) return fire_callback(args.callback, response, args); +var div = $(args.target); +assert(div, "Could not find target DIV: " + args.target); +var html = ''; +if (response.Rows && response.Rows.Row) { +var rows = always_array( response.Rows.Row ); +for (var idx = 0, len = rows.length; idx < len; idx++) { +var row = rows[idx]; +html += this.get_article_preview( row, args ); +} +if (args.more && (rows.length == args.limit)) { +html += large_icon_button('page_white_put.png', 'More...', "Blog.more(this, "+encode_object(args)+")") + '
'; +html += spacer(1,15) + '
'; +} +if (args.after) html += args.after; +} +else if (response.Code != 0) { +html = 'Search Error: ' . response.Code + ': ' + response.Description; +} +else { +html = args.none_found_msg || 'No articles found.'; +} +div.innerHTML = html; +}, +more: function(div, args) { +args.offset += args.limit; +Debug.trace('blog', "More Args: " + dumper(args)); +div.innerHTML = ''; +effect_api_get( 'article_search', args, [this, 'more_response'], { _search_args: args, _div: div } ); +}, +more_response: function(response, tx) { +var args = tx._search_args; +var button = tx._div; +var html = ''; +if (response.Rows && response.Rows.Row) { +var rows = always_array( response.Rows.Row ); +for (var idx = 0, len = rows.length; idx < len; idx++) { +var row = rows[idx]; +html += this.get_article_preview( row, args ); +} +if (args.more && (rows.length == args.limit)) { +html += large_icon_button('page_white_put.png', 'More...', "Blog.more(this, "+encode_object(args)+")") + '
'; +html += spacer(1,15) + '
'; +} +} +else if (response.Code != 0) { +html = 'Search Error: ' . response.Code + ': ' + response.Description; +} +else { +html = args.none_found_msg || 'No more articles found.'; +} +var div = document.createElement('div'); +div.innerHTML = html; +button.parentNode.replaceChild( div, button ); +} +}; +function make_tag_links(csv, max, base_url) { +if (!base_url) base_url = ''; +var tags = csv.split(/\,\s*/); +var append = ''; +if (max && (tags.length > max)) { +tags.length = max; +append = '...'; +} +var html = ''; +for (var idx = 0, len = tags.length; idx < len; idx++) { +html += ''+tags[idx]+''; +if (idx < len - 1) html += ', '; +} +html += append; +return html; +} +function get_url_friendly_title(title) { +title = title.toString().replace(/\W+/g, '_'); +if (title.length > 40) title = title.substring(0, 40); +title = title.replace(/^_+/, ''); +title = title.replace(/_+$/, ''); +return title; +} +function get_full_url(url) { +if (url.match(/^\#/)) { +var parts = window.location.href.split(/\#/); +url = parts[0] + url; +} +return url; +} +var Comments = { +comments_per_page: 10, +get: function(page_id) { +var html = ''; +html += '
'; +html += '
Comments'; +html += '
'; +html += '
'; +html += '
'; +setTimeout( function() { Comments.search({ page_id: page_id }); }, 1 ); +return html; +}, +search: function(args) { +if (!args.limit) args.limit = this.comments_per_page; +if (!args.offset) args.offset = 0; +assert(args.page_id, "Comments.search: No page_id specified"); +args.format = 'xml'; +this.last_search = args; +effect_api_get( 'comments_get', args, [this, 'search_response'], { _search_args: args } ); +}, +research: function(offset) { +var args = this.last_search; +if (!args) return; +args.offset = offset; +effect_api_get( 'comments_get', args, [this, 'search_response'], { _search_args: args } ); +}, +search_response: function(response, tx) { +this.comments = []; +var args = tx._search_args; +if (args.callback) return fire_callback(args.callback, response, args); +var html = ''; +html += '
' + +large_icon_button( 'comment_edit.png', 'Post Comment...', "Comments.add('"+args.page_id+"')" ) + '
'; +if (args.page_id.match(/^Article\//)) { +html += '
' + icon('feed.png', 'RSS', '/effect/api/comment_feed/' + args.page_id + '.rss', 'Comments RSS Feed') + '
'; +} +if (response.Items && response.Items.Item && response.List && response.List.length) { +html += ''; +html += '
'; +var items = this.comments = always_array( response.Items.Item ); +for (var idx = 0, len = items.length; idx < len; idx++) { +var item = items[idx]; +var extra_classes = (args.highlight && (args.highlight == item.ID)) ? ' highlight' : ''; +html += '
'; +html += '
'; +if (item.Username) html += ''; +html += '' + item.Name.toString().toUpperCase() + ''; +if (item.Username) html += ''; +html += ', ' + get_short_date_time(item.Date) + '
'; +html += '
'; +html += this.get_comment_controls( args.page_id, item ); +html += '
'; +html += '
'; +html += '
' + item.Comment + '
'; +html += '
'; +html += ''; +if (item.LastReply && ((item.LastReply >= time_now() - (86400 * 7)) || (session.username && (session.username == item.Username)))) { +setTimeout( "Comments.show_replies('"+args.page_id+"','"+item.ID+"')", 1 ); +} +} +} +else { +} +$( 'd_comments_' + args.page_id ).innerHTML = html; +}, +get_control: function(icon, code, text, status_text) { +if (!icon.match(/\.\w+$/)) icon += '.gif'; +return '' + code_link(code, text, status_text) + ''; +}, +get_comment_controls: function(page_id, comment) { +var html = ''; +var spacer_txt = '  |  '; +if (session.user) { +html += this.get_control('comment', "Comments.reply('"+page_id+"','"+comment.ID+"')", 'Reply') + spacer_txt; +} +if (comment.Replies) { +if (comment._replies_visible) html += this.get_control('magnify_minus', "Comments.hide_replies('"+page_id+"','"+comment.ID+"')", 'Hide Replies'); +else html += this.get_control('magnify_plus', "Comments.show_replies('"+page_id+"','"+comment.ID+"')", 'Show Replies ('+comment.Replies+')'); +if (session.user) html += spacer_txt; +} +if (session.user) { +html += this.get_control( +'star', +"Comments.like('"+page_id+"','"+comment.ID+"')", +'Like' + (comment.Like ? (' ('+comment.Like+')') : ''), +comment.Like ? (comment.Like + ' ' + ((comment.Like == 1) ? 'person likes this' : 'people like this')) : 'I like this comment' +) + spacer_txt; +if (is_admin()) html += this.get_control('trash', "Comments._delete('"+page_id+"','"+comment.ID+"')", 'Delete') + spacer_txt; +html += this.get_control('warning', "Comments.report('"+page_id+"','"+comment.ID+"')", 'Report Abuse'); +} +return html; +}, +reply: function(page_id, comment_id) { +hide_popup_dialog(); +delete session.progress; +var comment = find_object( this.comments, { ID: comment_id } ); +var html = ''; +html += '
'; +html += '\n \n \n \n'); + }; + __out.push('\n\n'); + __out.push(require('templates/clients/modules/sub_header').call(this, { + heading: t("Ride Request") + })); + __out.push('\n\n\n
\n
\n
\n
\n \n \n \n \n
\n\n
'; +html += '
Reply to Comment by "'+comment.Name+'"
'; +html += '
'; +var name = this.get_name(); +html += '

Posted by: ' + name; +if (!session.user) html += ' → Create Account'; +html += '


'; +html += ''; +html += Blog.edit_caption; +html += '
'; +html += '

'; +html += ''; +html += ''; +html += ''; +html += '
' + large_icon_button('x', 'Cancel', "hide_popup_dialog()") + ' ' + large_icon_button('check', 'Post Reply', "Comments.post_reply('"+page_id+"','"+comment_id+"')") + '
'; +html += '
'; +html += ''; +session.hooks.keys[ESC_KEY] = 'hide_popup_dialog'; +safe_focus( 'fe_comment_body' ); +show_popup_dialog(600, 300, html); +}, +post_reply: function(page_id, comment_id) { +var value = $('fe_comment_body').value; +if (!value) return; +hide_popup_dialog(); +show_progress_dialog(1, "Posting reply..."); +var name = this.get_name(); +effect_api_mod_touch('comment_replies_get'); +effect_api_send('comment_post_reply', { +PageID: page_id, +CommentID: comment_id, +Username: session.username || '', +Name: name, +Comment: value, +PageURL: location.href +}, [this, 'post_reply_finish'], { _page_id: page_id, _comment_id: comment_id } ); +}, +post_reply_finish: function(response, tx) { +hide_popup_dialog(); +var page_id = tx._page_id; +var comment_id = tx._comment_id; +var comment = find_object( this.comments, { ID: comment_id } ); +do_message('success', "Comment reply posted successfully."); +this.show_replies(page_id, comment_id); +if (!comment.Replies) comment.Replies = 1; else comment.Replies++; +$('d_comment_controls_'+comment_id).innerHTML = this.get_comment_controls( page_id, comment ); +}, +show_replies: function(page_id, comment_id) { +var comment = find_object( this.comments, { ID: comment_id } ); +if (!comment._replies_visible) { +$('d_comment_replies_' + comment_id).show().innerHTML = ''; +} +var args = { page_id: page_id, comment_id: comment_id, offset: 0, limit: 100 }; +effect_api_get( 'comment_replies_get', args, [this, 'receive_replies_response'], { _search_args: args } ); +}, +receive_replies_response: function(response, tx) { +var page_id = tx._search_args.page_id; +var comment_id = tx._search_args.comment_id; +var comment = find_object( this.comments, { ID: comment_id } ); +var html = ''; +var replies = always_array( response.Items.Item ); +for (var idx = 0, len = replies.length; idx < len; idx++) { +var reply = replies[idx]; +html += get_chat_balloon( +(reply.Username == session.username) ? 'blue' : 'grey', +reply.Username, +reply.Comment.replace(/^]*?>(.+)<\/div>$/i, '$1') +); +} +$('d_comment_replies_' + comment_id).innerHTML = html; +if (!comment._replies_visible) { +$('d_comment_replies_' + comment_id).hide(); +animate_div_visibility( 'd_comment_replies_' + comment_id, true ); +} +comment._replies_visible = true; +$('d_comment_controls_'+comment_id).innerHTML = this.get_comment_controls( page_id, comment ); +}, +hide_replies: function(page_id, comment_id) { +var comment = find_object( this.comments, { ID: comment_id } ); +if (comment._replies_visible) { +animate_div_visibility( 'd_comment_replies_' + comment_id, false ); +comment._replies_visible = false; +$('d_comment_controls_'+comment_id).innerHTML = this.get_comment_controls( page_id, comment ); +} +}, +like: function(page_id, comment_id) { +effect_api_mod_touch('comments_get'); +effect_api_send('comment_like', { +PageID: page_id, +CommentID: comment_id +}, [this, 'like_finish'], { _page_id: page_id, _comment_id: comment_id, _on_error: [this, 'like_error'] } ); +}, +like_error: function(response, tx) { +if (response.Code == 'comment_already_like') do_message('error', "You already like this comment."); +else do_error( response.Description ); +}, +like_finish: function(resopnse, tx) { +var page_id = tx._page_id; +var comment_id = tx._comment_id; +var comment = find_object( this.comments, { ID: comment_id } ); +do_message('success', "You now like this comment."); +if (!comment.Like) comment.Like = 1; else comment.Like++; +$('d_comment_controls_'+comment_id).innerHTML = this.get_comment_controls( page_id, comment ); +}, +add: function(page_id) { +hide_popup_dialog(); +delete session.progress; +var html = ''; +html += '
'; +html += '\n \n \n \n \n \n \n \n \n \n '); + }, this); + __out.push('\n\n
\n
'; +html += '
Post New Comment
'; +html += '
'; +var name = this.get_name(); +html += '

Posted by: ' + name; +if (!session.user) html += ' → Create Account'; +html += '


'; +html += ''; +html += Blog.edit_caption; +html += '
'; +html += '

'; +html += ''; +html += ''; +html += ''; +html += '
' + large_icon_button('x', 'Cancel', "hide_popup_dialog()") + ' ' + large_icon_button('check', 'Post Comment', "Comments.post('"+page_id+"')") + '
'; +html += '
'; +html += ''; +session.hooks.keys[ESC_KEY] = 'hide_popup_dialog'; +safe_focus( 'fe_comment_body' ); +show_popup_dialog(600, 300, html); +}, +report: function(page_id, comment_id) { +if (confirm('Are you sure you want to report this comment to the site administrators as abusive and/or spam?')) { +effect_api_send('comment_report_abuse', { +PageID: page_id, +CommentID: comment_id +}, [this, 'report_finish'], { _page_id: page_id, _comment_id: comment_id } ); +} +}, +report_finish: function(response, tx) { +do_message('success', 'Your abuse report has been received, and will be evaluated by the site administrators.'); +}, +_delete: function(page_id, comment_id) { +if (confirm('Are you sure you want to permanently delete this comment?')) { +effect_api_mod_touch('comments_get'); +effect_api_send('comment_delete', { +PageID: page_id, +CommentID: comment_id +}, [this, 'delete_finish'], { _page_id: page_id, _comment_id: comment_id } ); +} +}, +delete_finish: function(response, tx) { +do_message('success', 'The comment was deleted successfully.'); +var page_id = tx._page_id; +this.search({ page_id: page_id }); +}, +get_name: function() { +var name = '(Anonymous)'; +if (session.user) { +if (get_bool_pref('public_profile')) name = session.user.FullName; +else name = session.username; +} +return name; +}, +post: function(page_id) { +var value = $('fe_comment_body').value; +if (!value) return; +hide_popup_dialog(); +show_progress_dialog(1, "Posting comment..."); +var name = this.get_name(); +effect_api_mod_touch('comments_get'); +effect_api_send('comment_post', { +PageID: page_id, +Username: session.username || '', +Name: name, +Comment: value +}, [this, 'post_finish'], { _page_id: page_id } ); +}, +post_finish: function(response, tx) { +hide_popup_dialog(); +var comment_id = response.CommentID; +var page_id = tx._page_id; +this.search({ page_id: page_id, highlight: comment_id }); +} +}; +Class.create( 'Menu', { +id: '', +menu: null, +__construct: function(id) { +this.id = id; +}, +load: function() { +if (!this.menu) { +this.menu = $(this.id); +assert( !!this.menu, "Could not locate DOM element: " + this.id ); +} +}, +get_value: function() { +this.load(); +return this.menu.options[this.menu.selectedIndex].value; +}, +set_value: function(value, auto_add) { +value = str_value(value); +this.load(); +for (var idx = 0, len = this.menu.options.length; idx < len; idx++) { +if (this.menu.options[idx].value == value) { +this.menu.selectedIndex = idx; +return true; +} +} +if (auto_add) { +this.menu.options[this.menu.options.length] = new Option(value, value); +this.menu.selectedIndex = this.menu.options.length - 1; +return true; +} +return false; +}, +disable: function() { +this.load(); +this.menu.disabled = true; +this.menu.setAttribute( 'disabled', 'disabled' ); +}, +enable: function() { +this.load(); +this.menu.setAttribute( 'disabled', '' ); +this.menu.disabled = false; +}, +populate: function(items, sel_value) { +this.load(); +this.menu.options.length = 0; +for (var idx = 0, len = items.length; idx < len; idx++) { +var item = items[idx]; +var item_name = ''; +var item_value = ''; +if (isa_hash(item)) { +item_name = item.label; +item_value = item.data; +} +else if (isa_array(item)) { +item_name = item[0]; +item_value = item[1]; +} +else { +item_name = item_value = item; +} +this.menu.options[ this.menu.options.length ] = new Option( item_name, item_value ); +if (item_value == sel_value) this.menu.selectedIndex = idx; +} +} +} ); +Class.subclass( Menu, 'MultiMenu', { +__static: { +toggle_type: function(id) { +var menu = $(id); +assert(menu, "Could not find menu in DOM: " + id); +if (menu.disabled) return; +var obj = MenuManager.find(id); +assert(obj, "Could not find menu in MenuManager: " + id); +var div = $( 'd_inner_' + id ); +var ic = $( 'ic_' + id ); +var is_multiple = (ic.src.indexOf('contract') > -1); +obj.multi = !is_multiple; +var multiple_tag = !is_multiple ? +' multiple="multiple" size=5' : ''; +var items = []; +for (var idx = 0; idx < menu.options.length; idx++) { +var option = menu.options[idx]; +array_push( items, { +value: option.value, +text: option.text, +selected: option.selected +}); +} +var html = ''; +html += ''; +div.innerHTML = html; +ic.src = images_uri + '/menu_' + (is_multiple ? 'expand' : 'contract') + '.gif'; +obj.menu = null; +} +}, +attribs: null, +multi: false, +toggle: true, +__construct: function(id, attribs) { +this.id = id; +if (attribs) this.attribs = attribs; +}, +get_html: function(items, selected_csv, attribs) { +if (!items) items = []; +if (!selected_csv) selected_csv = ''; +if (attribs) this.attribs = attribs; +var selected = csv_to_hash(selected_csv); +this.menu = null; +if (num_keys(selected) > 1) this.multi = true; +var html = '
'; +html += ''; +html += ''; +html += ''; +if (this.toggle) html += ''; +html += '
' + spacer(1,1) + '
'+spacer(1,2)+'
'; +html += '
'; +return html; +}, +get_value: function() { +this.load(); +var value = ''; +for (var idx = 0; idx < this.menu.options.length; idx++) { +var option = this.menu.options[idx]; +if (option.selected && option.value.length) { +if (value.length > 0) value += ','; +value += option.value; +} +} +return value; +}, +set_value: function(value, auto_add) { +value = '' + value; +this.load(); +if (!value) { +value = ''; +for (var idx = 0; idx < this.menu.options.length; idx++) { +var option = this.menu.options[idx]; +option.selected = (option.value == value); +} +return; +} +var selected = csv_to_hash(value); +if ((num_keys(selected) > 1) && !this.multi) { +MultiMenu.toggle_type(this.id); +var self = this; +setTimeout( function() { +self.set_value(value, auto_add); +}, 1 ); +return; +} +for (var idx = 0; idx < this.menu.options.length; idx++) { +var option = this.menu.options[idx]; +option.selected = selected[option.value] ? true : false; +} +}, +populate: function(items, value) { +this.load(); +this.menu.options.length = 0; +if (!value) value = ''; +var selected = csv_to_hash(value); +for (var idx = 0, len = items.length; idx < len; idx++) { +var item = items[idx]; +var item_name = ''; +var item_value = ''; +if (isa_hash(item)) { +item_name = item.label; +item_value = item.data; +} +else if (isa_array(item)) { +item_name = item[0]; +item_value = item[1]; +} +else { +item_name = item_value = item; +} +var opt = new Option( item_name, item_value ); +this.menu.options[ this.menu.options.length ] = opt; +opt.selected = selected[item_value] ? true : false; +} +}, +collapse: function() { +if (this.multi) MultiMenu.toggle_type(this.id); +}, +expand: function() { +if (!this.multi) MultiMenu.toggle_type(this.id); +} +} ); +Class.create( 'MenuManager', { +__static: { +menus: {}, +register: function(menu) { +this.menus[ menu.id ] = menu; +return menu; +}, +find: function(id) { +return this.menus[id]; +} +} +} ); +Class.create( 'GrowlManager', { +lifetime: 10, +marginRight: 0, +marginTop: 0, +__construct: function() { +this.growls = []; +}, +growl: function(type, msg) { +if (find_object(this.growls, { type: type, msg: msg })) return; +var div = $(document.createElement('div')); +div.className = 'growl_message ' + type; +div.setOpacity(0.0); +div.innerHTML = '
' + msg + '
' + spacer(1,5) + '
'; +$('d_growl_wrapper').insertBefore( div, $('d_growl_top').nextSibling ); +var growl = { id:get_unique_id(), type: type, msg: msg, opacity:0.0, start:hires_time_now(), div:div }; +this.growls.push(growl); +this.handle_resize(); +this.animate(growl); +var self = this; +div.onclick = function() { +delete_object(self.growls, { id: growl.id }); +$('d_growl_wrapper').removeChild( div ); +}; +}, +animate: function(growl) { +if (growl.deleted) return; +var now = hires_time_now(); +var div = growl.div; +if (now - growl.start <= 0.5) { +div.setOpacity( tweenFrame(0.0, 1.0, (now - growl.start) * 2, 'EaseOut', 'Quadratic') ); +} +else if (now - growl.start <= this.lifetime) { +if (!growl._fully_opaque) { +div.setOpacity( 1.0 ); +growl._fully_opaque = true; +} +} +else if (now - growl.start <= this.lifetime + 1.0) { +div.setOpacity( tweenFrame(1.0, 0.0, (now - growl.start) - this.lifetime, 'EaseOut', 'Quadratic') ); +} +else { +delete_object(this.growls, { id: growl.id }); +$('d_growl_wrapper').removeChild( div ); +return; +} +var self = this; +setTimeout( function() { self.animate(growl); }, 33 ); +}, +handle_resize: function() { +var div = $('d_growl_wrapper'); +if (this.growls.length) { +var size = getInnerWindowSize(); +div.style.top = '' + (10 + this.marginTop) + 'px'; +div.style.left = '' + Math.floor((size.width - 310) - this.marginRight) + 'px'; +} +else { +div.style.left = '-2000px'; +} +} +} ); +window.$GR = new GrowlManager(); +if (window.addEventListener) { +window.addEventListener( "resize", function() { +$GR.handle_resize(); +}, false ); +} +else if (window.attachEvent && !ie6) { +window.attachEvent("onresize", function() { +$GR.handle_resize(); +}); +} +Class.create( 'Effect.Page', { +ID: '', +data: null, +active: false, +__construct: function(config) { +if (!config) return; +this.data = {}; +if (!config) config = {}; +for (var key in config) this[key] = config[key]; +this.div = $('page_' + this.ID); +assert(this.div, "Cannot find page div: page_" + this.ID); +}, +onInit: function() { +}, +onActivate: function() { +return true; +}, +onDeactivate: function() { +return true; +}, +show: function() { +this.div.show(); +}, +hide: function() { +this.div.hide(); +}, +gosub: function(anchor) { +} +} ); +Class.require( 'Effect.Page' ); +Class.create( 'Effect.PageManager', { +pages: null, +current_page_id: '', +on_demand: {}, +__construct: function(page_list) { +this.pages = []; +this.page_list = page_list; +for (var idx = 0, len = page_list.length; idx < len; idx++) { +Debug.trace( 'page', "Initializing page: " + page_list[idx].ID ); +if (Effect.Page[ page_list[idx].ID ]) { +var page = new Effect.Page[ page_list[idx].ID ]( page_list[idx] ); +page.onInit(); +this.pages.push(page); +} +else { +Debug.trace( 'page', 'Page ' + page_list[idx].ID + ' will be loaded on-demand' ); +} +} +}, +find: function(id) { +var page = find_object( this.pages, { ID: id } ); +if (!page) Debug.trace('PageManager', "Could not find page: " + id); +return page; +}, +notify_load: function(file, id) { +for (var idx = 0, len = this.page_list.length; idx < len; idx++) { +var page_config = this.page_list[idx]; +if (page_config.File == file) { +Debug.trace( 'page', "Initializing page on-demand: " + page_config.ID ); +var page = new Effect.Page[ page_config.ID ]( page_config ); +page.onInit(); +this.pages.push(page); +} +} +var self = this; +setTimeout( function() { +var result = self.activate(id, self.temp_args); +delete self.temp_args; +$('d_page_loading').hide(); +if (!result) { +$('page_'+id).hide(); +self.current_page_id = ''; +} +}, 1 ); +}, +activate: function(id, args) { +if (!find_object( this.pages, { ID: id } )) { +var page_config = find_object( this.page_list, { ID: id } ); +assert(!!page_config, "Page config not found: " + id ); +Debug.trace('page', "Loading file on-demand: " + page_config.File + " for page: " + id); +var url = '/effect/api/load_page/' + page_config.File + '?onafter=' + escape('page_manager.notify_load(\''+page_config.File+'\',\''+id+'\')'); +if (page_config.Requires) { +var files = page_config.Requires.split(/\,\s*/); +for (var idx = 0, len = files.length; idx < len; idx++) { +var filename = files[idx]; +if (!this.on_demand[filename]) { +Debug.trace('page', "Also loading file: " + filename); +url += '&file=' + filename; +this.on_demand[filename] = 1; +} +} +} +$('d_page_loading').show(); +this.temp_args = args; +load_script( url ); +return true; +} +$('page_'+id).show(); +var page = this.find(id); +page.active = true; +if (!args) args = []; +if (!isa_array(args)) args = [ args ]; +var result = page.onActivate.apply(page, args); +if (typeof(result) == 'boolean') return result; +else return alert("Page " + id + " onActivate did not return a boolean!"); +}, +deactivate: function(id, new_id) { +var page = this.find(id); +var result = page.onDeactivate(new_id); +if (result) { +$('page_'+id).hide(); +page.active = false; +} +return result; +}, +click: function(id, args) { +Debug.trace('page', "Switching pages to: " + id); +var old_id = this.current_page_id; +if (this.current_page_id) { +var result = this.deactivate( this.current_page_id, id ); +if (!result) return false; +} +this.current_page_id = id; +this.old_page_id = old_id; +window.scrollTo( 0, 0 ); +var result = this.activate(id, args); +if (!result) { +$('page_'+id).hide(); +this.current_page_id = ''; +} +return true; +} +} ); +Class.subclass( Effect.Page, "Effect.Page.Main", { +inited: false, +onActivate: function() { +Nav.bar( ['Main', 'EffectGames.com'] ); +Nav.title(''); +$('d_blog_news').innerHTML = loading_image(); +$('d_blog_community').innerHTML = loading_image(); +$('d_blog_featured').innerHTML = loading_image(); +Blog.search({ +stag: 'featured_game', +limit: 4, +full: 1, +callback: [this, 'receive_featured_games'] +}); +effect_api_get( 'get_site_info', { cat: 'pop_pub_games' }, [this, 'receive_pop_pub_games'], { } ); +Blog.search({ +stag: 'front_page', +limit: 5, +target: 'd_blog_news', +more: 1 +}); +Blog.search({ +path: '/community', +limit: 5, +target: 'd_blog_community', +more: 1 +}); +if (!this.inited) { +this.inited = true; +config.Strings.MainSlideshow.Slide = always_array( config.Strings.MainSlideshow.Slide ); +this.slide_idx = 0; +this.num_slides = config.Strings.MainSlideshow.Slide.length; +this.slide_div_num = 0; +this.slide_dir = 1; +this.bk_pos = -340; +this.bk_pos_target = -340; +this.slide_images = []; +for (var idx = 0, len = this.num_slides; idx < len; idx++) { +var url = images_uri + '/' + config.Strings.MainSlideshow.Slide[idx].Photo; +this.slide_images[idx] = new Image(); +this.slide_images[idx].src = png(url, true); +} +} +this.height_target = 470; +this.height_start = $('d_header').offsetHeight; +this.time_start = hires_time_now(); +this.duration = 0.75; +if (!this.timer) this.timer = setTimeout( '$P("Main").animate_mhs()', 33 ); +if (session.user) $('d_blurb_main').hide(); +else { +$('d_blurb_main').innerHTML = get_string('/Main/Blurb'); +$('d_blurb_main').show(); +} +return true; +}, +receive_pop_pub_games: function(response, tx) { +var html = ''; +if (response.Data && response.Data.Games && response.Data.Games.Game) { +var games = always_array( response.Data.Games.Game ); +for (var idx = 0, len = Math.min(games.length, 16); idx < len; idx++) { +var game = games[idx]; +html += '
' + +(game.Logo ? +user_image_thumbnail(game.Logo, 80, 60) : +'' +) + '
' + ww_fit_box(game.Title, 80, 2, session.em_width, 1) + '
'; +} +html += '
'; +} +else { +html += 'No active public games found! Why not create a new one?'; +} +$('d_main_pop_pub_games').innerHTML = html; +}, +receive_featured_games: function(response, tx) { +var html = ''; +if (response.Rows && response.Rows.Row) { +html += ''; +var rows = always_array( response.Rows.Row ); +for (var idx = 0, len = rows.length; idx < len; idx++) { +var row = rows[idx]; +var image_url = row.Params.featured_image; +if (image_url && image_url.match(/^(\w+)\/(\w+\.\w+)$/)) { +image_url = '/effect/api/view/users/' + RegExp.$1 + '/images/' + RegExp.$2; +} +if (idx % 2 == 0) html += ''; +html += ''; +if (idx % 2 == 1) html += ''; +} +if (rows.length % 2 == 1) { +html += ''; +html += ''; +} +html += '
'; +html += ''; +html += ''; +html += ''; +html += ''; +html += ''; +html += '
'; +html += ''; +html += '' + spacer(10,1) + ''; +html += ''; +html += ''; +html += '' + spacer(15,1) + '
'; +html += spacer(1,20); +html += '
'; +} +$('d_blog_featured').innerHTML = html; +}, +animate_mhs: function() { +var now = hires_time_now(); +if (now - this.time_start >= this.duration) { +$('d_header').style.height = '' + this.height_target + 'px'; +$('d_shadow').style.height = '' + this.height_target + 'px'; +delete this.timer; +} +else { +var height = tweenFrame(this.height_start, this.height_target, (now - this.time_start) / this.duration, 'EaseOut', 'Circular'); +$('d_header').style.height = '' + height + 'px'; +$('d_shadow').style.height = '' + height + 'px'; +this.timer = setTimeout( '$P("Main").animate_mhs()', 33 ); +} +}, +onDeactivate: function() { +$('d_blog_news').innerHTML = ''; +$('d_blog_community').innerHTML = ''; +this.height_target = 75; +this.height_start = $('d_header').offsetHeight; +this.time_start = hires_time_now(); +if (!this.timer) this.timer = setTimeout( '$P("Main").animate_mhs()', 33 ); +return true; +}, +draw_slide: function() { +if (this.slide_timer) return; +var slide = config.Strings.MainSlideshow.Slide[ this.slide_idx ]; +this.old_photo = $('d_header_slideshow_photo_' + this.slide_div_num); +this.old_text = $('d_header_slideshow_text_' + this.slide_div_num); +this.slide_div_num = 1 - this.slide_div_num; +this.new_photo = $('d_header_slideshow_photo_' + this.slide_div_num); +this.new_text = $('d_header_slideshow_text_' + this.slide_div_num); +this.new_photo.style.backgroundImage = 'url('+png(images_uri+'/'+slide.Photo, true)+')'; +this.new_photo.setOpacity(0.0); +var html = ''; +html += slide.Text; +this.slide_width = this.new_text.offsetWidth; +this.new_text.innerHTML = html; +if (this.slide_dir == 1) this.new_text.style.left = '' + this.slide_width + 'px'; +else this.new_text.style.left = '-' + this.slide_width + 'px'; +this.slide_time_start = hires_time_now(); +this.slide_timer = setTimeout( '$P("Main").animate_mhs_slide()', 33 ); +}, +animate_mhs_slide: function() { +var now = hires_time_now(); +if (now - this.slide_time_start >= this.duration) { +this.new_text.style.left = '0px'; +this.old_text.style.left = '-' + this.slide_width + 'px'; +this.new_photo.setOpacity( 1.0 ); +this.old_photo.setOpacity( 0.0 ); +delete this.slide_timer; +this.bk_pos = this.bk_pos_target; +} +else { +var value = tweenFrame(0.0, 1.0, (now - this.slide_time_start) / this.duration, 'EaseOut', 'Circular'); +if (this.slide_dir == 1) { +this.new_text.style.left = '' + Math.floor( this.slide_width - (this.slide_width * value) ) + 'px'; +this.old_text.style.left = '-' + Math.floor( this.slide_width * value ) + 'px'; +} +else { +this.new_text.style.left = '-' + Math.floor( this.slide_width - (this.slide_width * value) ) + 'px'; +this.old_text.style.left = '' + Math.floor( this.slide_width * value ) + 'px'; +} +this.new_photo.setOpacity( value ); +this.old_photo.setOpacity( 1.0 - value ); +var bkp = Math.floor( this.bk_pos + ((this.bk_pos_target - this.bk_pos) * value) ); +$('d_header').style.backgroundPosition = '' + bkp + 'px 0px'; +this.slide_timer = setTimeout( '$P("Main").animate_mhs_slide()', 33 ); +} +}, +prev_slide: function() { +this.bk_pos_target += 200; +this.slide_idx--; +if (this.slide_idx < 0) this.slide_idx += this.num_slides; +this.slide_dir = -1; +this.draw_slide(); +}, +next_slide: function() { +this.bk_pos_target -= 200; +this.slide_idx++; +if (this.slide_idx >= this.num_slides) this.slide_idx -= this.num_slides; +this.slide_dir = 1; +this.draw_slide(); +} +} ); +Class.subclass( Effect.Page, "Effect.Page.PublicGameList", { +onActivate: function() { +Nav.bar( +['Main', 'EffectGames.com'], +['PublicGameList', "All Public Games"] +); +Nav.title( "List of All Public Game Projects" ); +effect_api_get( 'get_site_info', { cat: 'all_pub_games' }, [this, 'receive_all_pub_games'], { } ); +this.div.innerHTML = loading_image(); +return true; +}, +onDeactivate: function() { +this.div.innerHTML = ''; +return true; +}, +receive_all_pub_games: function(response, tx) { +var html = ''; +html += '

List of All Public Game Projects

'; +html += '
This is the complete list of public games currently being built by our users, presented in alphabetical order. Maybe they could use some help! Check out the game project pages and see (requires user account).
'; +if (response.Data && response.Data.Games && response.Data.Games.Game) { +var games = always_array( response.Data.Games.Game ); +for (var idx = 0, len = games.length; idx < len; idx++) { +var game = games[idx]; +html += '
' + +(game.Logo ? +user_image_thumbnail(game.Logo, 80, 60) : +'' +) + '
' + ww_fit_box(game.Title, 80, 2, session.em_width, 1) + '
'; +} +html += '
'; +} +else { +html += 'No public games found! Why not create a new one?'; +} +this.div.innerHTML = html; +} +} ); +Class.subclass( Effect.Page, "Effect.Page.Search", { +onActivate: function(args) { +if (!args) args = {}; +var search_text = args.q; +var start = args.s || 0; +if (!start) start = 0; +var title = 'Search results for "'+search_text+'"'; +Nav.bar( +['Main', 'EffectGames.com'], +['Search?q=' + escape(search_text), "Search Results"] +); +Nav.title( title ); +this.last_search_text = search_text; +$('d_article_search').innerHTML = loading_image(); +load_script( 'http://www.google.com/uds/GwebSearch?callback=receive_google_search_results&context=0&lstkp=0&rsz=large&hl=en&source=gsc&gss=.com&sig=&q='+escape(search_text)+'%20site%3Ahttp%3A%2F%2Fwww.effectgames.com%2F&key=notsupplied&v=1.0&start='+start+'&nocache=' + (new Date()).getTime() ); +$('h_article_search').innerHTML = title; +return true; +}, +onDeactivate: function(new_page) { +$('fe_search_bar').value = ''; +$('d_article_search').innerHTML = ''; +return true; +} +} ); +function do_search_bar() { +var search_text = $('fe_search_bar').value; +if (search_text.length) { +Nav.go('Search?q=' + escape(search_text)); +} +} +function receive_google_search_results(context, response) { +var html = ''; +html += '
Powered by
'; +if (response.results.length) { +for (var idx = 0, len = response.results.length; idx < len; idx++) { +var row = response.results[idx]; +var url = row.unescapedUrl.replace(/^.+article\.psp\.html/, '#Article'); +html += '
'; +html += ''; +html += '
' + row.content + '
'; +html += '
'; +} +} +else { +html += 'No results found.'; +} +if (response.cursor.pages) { +html += '
Page: '; +for (var idx = 0, len = response.cursor.pages.length; idx < len; idx++) { +html += ''; +var page = response.cursor.pages[idx]; +var url = '#Search?q=' + escape($P('Search').last_search_text) + '&s=' + page.start; +if (response.cursor.currentPageIndex != idx) html += ''; +else html += ''; +html += page.label; +if (response.cursor.currentPageIndex != idx) html += ''; +else html += ''; +html += ''; +} +html += '
'; +} +$('d_article_search').innerHTML = html; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/index.js b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/index.js new file mode 100644 index 0000000..8b164a4 --- /dev/null +++ b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/index.js @@ -0,0 +1 @@ +exports.ZeParser = require('./ZeParser').ZeParser; diff --git a/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/package.json b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/package.json new file mode 100644 index 0000000..ec0a57b --- /dev/null +++ b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/package.json @@ -0,0 +1,30 @@ +{ + "author": { + "name": "Peter van der Zee", + "url": "http://qfox.nl/" + }, + "name": "zeparser", + "description": "My JavaScript parser", + "version": "0.0.5", + "homepage": "https://github.com/qfox/ZeParser/", + "repository": { + "type": "git", + "url": "git://github.com/qfox/ZeParser.git" + }, + "main": "./index", + "engines": { + "node": "*" + }, + "dependencies": {}, + "devDependencies": {}, + "_id": "zeparser@0.0.5", + "optionalDependencies": {}, + "_engineSupported": true, + "_npmVersion": "1.1.12", + "_nodeVersion": "v0.6.14", + "_defaultsLoaded": true, + "dist": { + "shasum": "c515aa7cea65ee11c178f350ddf5d8e0939b12d9" + }, + "_from": "zeparser@0.0.5" +} diff --git a/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/test-parser.html b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/test-parser.html new file mode 100755 index 0000000..1ff5ff4 --- /dev/null +++ b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/test-parser.html @@ -0,0 +1,26 @@ + + + + Parser Test Suite Page + + + + (c) qfox.nl
+ Parser test suite
+
Running...
+ + + + + + + \ No newline at end of file diff --git a/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/test-tokenizer.html b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/test-tokenizer.html new file mode 100755 index 0000000..0e0d1b1 --- /dev/null +++ b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/test-tokenizer.html @@ -0,0 +1,23 @@ + + + + Tokenizer Test Suite Page + + + + (c) qfox.nl
+ + + + + + \ No newline at end of file diff --git a/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/tests.js b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/tests.js new file mode 100644 index 0000000..8a4138b --- /dev/null +++ b/node_modules/socket.io-client/node_modules/active-x-obfuscator/node_modules/zeparser/tests.js @@ -0,0 +1,478 @@ +// tests for both the tokenizer and parser. Parser test results could be checked tighter. +// api: [input, token-output-count, ?regex-hints, desc] +// regex-hints are for tokenizer, will tell for each token whether it might parse regex or not (parser's job) +var Tests = [ + +["var abc;", 4, "Variable Declaration"], +["var abc = 5;", 8, "Variable Declaration, Assignment"], +["/* */", 1, "Block Comment"], +["/** **/", 1, "JSDoc-style Comment"], +["var f = function(){;};", 13, "Assignment, Function Expression"], +["hi; // moo", 4, "Trailing Line Comment"], +["hi; // moo\n;", 6, "Trailing Line Comment, Linefeed, `;`"], +["var varwithfunction;", 4, "Variable Declaration, Identifier Containing Reserved Words, `;`"], +["a + b;", 6, "Addition/Concatenation"], + +["'a'", 1, "Single-Quoted String"], +["'a';", 2, "Single-Quoted String, `;`"], // Taken from the parser test suite. + +["'a\\n'", 1, "Single-Quoted String With Escaped Linefeed"], +["'a\\n';", 2, "Single-Quoted String With Escaped Linefeed, `;`"], // Taken from the parser test suite. + +["\"a\"", 1, "Double-Quoted String"], +["\"a\";", 2, "Double-Quoted String, `;`"], // Taken from the parser test suite. + +["\"a\\n\"", 1, "Double-Quoted String With Escaped Linefeed"], +["\"a\\n\";", 2, "Double-Quoted String With Escaped Linefeed, `;`"], // Taken from the parser test suite. + +["500", 1, "Integer"], +["500;", 2, "Integer, `;`"], // Taken from the parser test suite. + +["500.", 1, "Double With Trailing Decimal Point"], +["500.;", 2, "Double With Trailing Decimal Point"], // Taken from the parser test suite. + +["500.432", 1, "Double With Decimal Component"], +["500.432;", 2, "Double With Decimal Component, `;`"], // Taken from the parser test suite. + +[".432432", 1, "Number, 0 < Double < 1"], +[".432432;", 2, "Number, 0 < Double < 1, `;`"], // Taken from the parser test suite. + +["(a,b,c)", 7, "Parentheses, Comma-separated identifiers"], +["(a,b,c);", 8, "Parentheses, Comma-separated identifiers, `;`"], // Taken from the parser test suite. + +["[1,2,abc]", 7, "Array literal"], +["[1,2,abc];", 8, "Array literal, `;`"], // Taken from the parser test suite. + +["{a:1,\"b\":2,c:c}", 13, "Object literal"], +["var o = {a:1,\"b\":2,c:c};", 20, "Assignment, Object Literal, `;`"], // Taken from the parser test suite. + +["var x;\nvar y;", 9, "2 Variable Declarations, Multiple lines"], +["var x;\nfunction n(){ }", 13, "Variable, Linefeed, Function Declaration"], +["var x;\nfunction n(abc){ }", 14, "Variable, Linefeed, Function Declaration With One Argument"], +["var x;\nfunction n(abc, def){ }", 17, "Variable, Linefeed, Function Declaration With Multiple Arguments"], +["function n(){ \"hello\"; }", 11, "Function Declaration, Body"], + +["/a/;", 2, [true, false], "RegExp Literal, `;`"], +["/a/b;", 2, [true, true], "RegExp Literal, Flags, `;`"], +["++x;", 3, "Unary Increment, Prefix, `;`"], +[" / /;", 3, [true, true, false], "RegExp, Leading Whitespace, `;`"], +["/ / / / /", 5, [true, false, false, false, true], "RegExp Containing One Space, Space, Division, Space, RegExp Containing One Space"], + +// Taken from the parser test suite. + +["\"var\";", 2, "Keyword String, `;`"], +["\"variable\";", 2, "String Beginning With Keyword, `;`"], +["\"somevariable\";", 2, "String Containing Keyword, `;`"], +["\"somevar\";", 2, "String Ending With Keyword, `;`"], + +["var varwithfunction;", 4, "Keywords should not be matched in identifiers"], + +["var o = {a:1};", 12, "Object Literal With Unquoted Property"], +["var o = {\"b\":2};", 12, "Object Literal With Quoted Property"], +["var o = {c:c};", 12, "Object Literal With Equivalent Property Name and Identifier"], + +["/a/ / /b/;", 6, [true, true, false, false, true, false], "RegExp, Division, RegExp, `;`"], +["a/b/c;", 6, "Triple Division (Identifier / Identifier / Identifier)"], + +["+function(){/regex/;};", 9, [false, false, false, false, false, true, false, false, false], "Unary `+` Operator, Function Expression Containing RegExp and Semicolon, `;`"], + +// Line Terminators. +["\r\n", 1, "CRLF Line Ending = 1 Linefeed"], +["\r", 1, "CR Line Ending = 1 Linefeed"], +["\n", 1, "LF Line Ending = 1 Linefeed"], +["\r\n\n\u2028\u2029\r", 5, "Various Line Terminators"], + +// Whitespace. +["a \t\u000b\u000c\u00a0\uFFFFb", 8, "Whitespace"], + +// Comments. +["//foo!@#^&$1234\nbar;", 4, "Line Comment, Linefeed, Identifier, `;`"], +["/* abcd!@#@$* { } && null*/;", 2, "Single-Line Block Comment, `;`"], +["/*foo\nbar*/;", 2, "Multi-Line Block Comment, `;`"], +["/*x*x*/;", 2, "Block Comment With Asterisks, `;`"], +["/**/;", 2, "Empty Comment, `;`"], + +// Identifiers. +["x;", 2, "Single-Character Identifier, `;`"], +["_x;", 2, "Identifier With Leading `_`, `;`"], +["xyz;", 2, "Identifier With Letters Only, `;`"], +["$x;", 2, "Identifier With Leading `$`, `;`"], +["x5;", 2, "Identifier With Number As Second Character, `;`"], +["x_y;", 2, "Identifier Containing `_`, `;`"], +["x+5;", 4, "Identifier, Binary `+` Operator, Identifier, `;`"], +["xyz123;", 2, "Alphanumeric Identifier, `;`"], +["x1y1z1;", 2, "Alternating Alphanumeric Identifier, `;`"], +["foo\\u00d8bar;", 2, "Identifier With Unicode Escape Sequence (`\\uXXXX`), `;`"], +["f\u00d8\u00d8bar;", 2, "Identifier With Embedded Unicode Character"], + +// Numbers. +["5;", 2, "Integer, `;`"], +["5.5;", 2, "Double, `;`"], +["0;", 2, "Integer Zero, `;`"], +["0.0;", 2, "Double Zero, `;`"], +["0.001;", 2, "0 < Decimalized Double < 1, `;`"], +["1.e2;", 2, "Integer With Decimal and Exponential Component (`e`), `;`"], +["1.e-2;", 2, "Integer With Decimal and Negative Exponential Component, `;`"], +["1.E2;", 2, "Integer With Decimal and Uppercase Exponential Component (`E`), `;`"], +["1.E-2;", 2, "Integer With Decimal and Uppercase Negative Exponential Component, `;`"], +[".5;", 2, "0 < Double < 1, `;`"], +[".5e3;", 2, "(0 < Double < 1) With Exponential Component"], +[".5e-3;", 2, "(0 < Double < 1) With Negative Exponential Component"], +["0.5e3;", 2, "(0 < Decimalized Double < 1) With Exponential Component"], +["55;", 2, "Two-Digit Integer, `;`"], +["123;", 2, "Three-Digit Integer, `;`"], +["55.55;", 2, "Two-Digit Double, `;`"], +["55.55e10;", 2, "Two-Digit Double With Exponential Component, `;`"], +["123.456;", 2, "Three-Digit Double, `;`"], +["1+e;", 4, "Additive Expression, `;`"], +["0x01;", 2, "Hexadecimal `1` With 1 Leading Zero, `;`"], +["0xcafe;", 2, "Hexadecimal `51966`, `;`"], +["0x12345678;", 2, "Hexadecimal `305419896`, `;`"], +["0x1234ABCD;", 2, "Hexadecimal `305441741` With Uppercase Letters, `;`"], +["0x0001;", 2, "Hexadecimal `1` with 3 Leading Zeros, `;`"], + +// Strings. +["\"foo\";", 2, "Multi-Character Double-Quoted String, `;`"], +["\"a\\n\";", 2, "Double-Quoted String Containing Linefeed, `;`"], +["\'foo\';", 2, "Single-Quoted String, `;`"], +["'a\\n';", 2, "Single-Quoted String Containing Linefeed, `;`"], +["\"x\";", 2, "Single-Character Double-Quoted String, `;`"], +["'';", 2, "Empty Single-Quoted String, `;`"], +["\"foo\\tbar\";", 2, "Double-Quoted String With Tab Character, `;`"], +["\"!@#$%^&*()_+{}[]\";", 2, "Double-Quoted String Containing Punctuators, `;`"], +["\"/*test*/\";", 2, "Double-Quoted String Containing Block Comment, `;`"], +["\"//test\";", 2, "Double-Quoted String Containing Line Comment, `;`"], +["\"\\\\\";", 2, "Double-Quoted String Containing Reverse Solidus, `;`"], +["\"\\u0001\";", 2, "Double-Quoted String Containing Numeric Unicode Escape Sequence, `;`"], +["\"\\uFEFF\";", 2, "Double-Quoted String Containing Alphanumeric Unicode Escape Sequence, `;`"], +["\"\\u10002\";", 2, "Double-Quoted String Containing 5-Digit Unicode Escape Sequence, `;`"], +["\"\\x55\";", 2, "Double-Quoted String Containing Hex Escape Sequence, `;`"], +["\"\\x55a\";", 2, "Double-Quoted String Containing Hex Escape Sequence and Additional Character, `;`"], +["\"a\\\\nb\";", 2, "Double-Quoted String Containing Escaped Linefeed, `;`"], +["\";\"", 1, "Double-Quoted String Containing `;`"], +["\"a\\\nb\";", 2, "Double-Quoted String Containing Reverse Solidus and Linefeed, `;`"], +["'\\\\'+ ''", 4, "Single-Quoted String Containing Reverse Solidus, `+`, Empty Single-Quoted String"], + +// `null`, `true`, and `false`. +["null;", 2, "`null`, `;`"], +["true;", 2, "`true`, `;`"], +["false;", 2, "`false`, `;`"], + +// RegExps +["/a/;", 2, [true, true], "Single-Character RegExp, `;`"], +["/abc/;", 2, [true, true], "Multi-Character RegExp, `;`"], +["/abc[a-z]*def/g;", 2, [true, true], "RegExp Containing Character Range and Quantifier, `;`"], +["/\\b/;", 2, [true, true], "RegExp Containing Control Character, `;`"], +["/[a-zA-Z]/;", 2, [true, true], "RegExp Containing Extended Character Range, `;`"], +["/foo(.*)/g;", 2, [true, false], "RegExp Containing Capturing Group and Quantifier, `;`"], + +// Array Literals. +["[];", 3, "Empty Array, `;`"], +["[\b\n\f\r\t\x20];", 9, "Array Containing Whitespace, `;`"], +["[1];", 4, "Array Containing 1 Element, `;`"], +["[1,2];", 6, "Array Containing 2 Elements, `;`"], +["[1,2,,];", 8, "Array Containing 2 Elisions, `;`"], +["[1,2,3];", 8, "Array Containing 3 Elements, `;`"], +["[1,2,3,,,];", 11, "Array Containing 3 Elisions, `;`"], + +// Object Literals. +["({x:5});", 8, "Object Literal Containing 1 Member; `;`"], +["({x:5,y:6});", 12, "Object Literal Containing 2 Members, `;`"], +["({x:5,});", 9, "Object Literal Containing 1 Member and Trailing Comma, `;`"], +["({if:5});", 8, "Object Literal Containing Reserved Word Property Name, `;`"], +["({ get x() {42;} });", 17, "Object Literal Containing Getter, `;`"], +["({ set y(a) {1;} });", 18, "Object Literal Containing Setter, `;`"], + +// Member Expressions. +["o.m;", 4, "Dot Member Accessor, `;`"], +["o['m'];", 5, "Square Bracket Member Accessor, `;`"], +["o['n']['m'];", 8, "Nested Square Bracket Member Accessor, `;`"], +["o.n.m;", 6, "Nested Dot Member Accessor, `;`"], +["o.if;", 4, "Dot Reserved Property Name Accessor, `;`"], + +// Function Calls. +["f();", 4, "Function Call Operator, `;`"], +["f(x);", 5, "Function Call Operator With 1 Argument, `;`"], +["f(x,y);", 7, "Function Call Operator With Multiple Arguments, `;`"], +["o.m();", 6, "Dot Member Accessor, Function Call, `;`"], +["o['m']();", 7, "Square Bracket Member Accessor, Function Call, `;`"], +["o.m(x);", 7, "Dot Member Accessor, Function Call With 1 Argument, `;`"], +["o['m'](x);", 8, "Square Bracket Member Accessor, Function Call With 1 Argument, `;`"], +["o.m(x,y);", 9, "Dot Member Accessor, Function Call With 2 Arguments, `;`"], +["o['m'](x,y);", 10, "Square Bracket Member Accessor, Function Call With 2 Arguments, `;`"], +["f(x)(y);", 8, "Nested Function Call With 1 Argument Each, `;`"], +["f().x;", 6, "Function Call, Dot Member Accessor, `;`"], + +// `eval` Function. +["eval('x');", 5, "`eval` Invocation With 1 Argument, `;`"], +["(eval)('x');", 7, "Direct `eval` Call Example, `;`"], +["(1,eval)('x');", 9, "Indirect `eval` Call Example, `;`"], +["eval(x,y);", 7, "`eval` Invocation With 2 Arguments, `;`"], + +// `new` Operator. +["new f();", 6, "`new` Operator, Function Call, `;`"], +["new o;", 4, "`new` Operator, Identifier, `;`"], +["new o.m;", 6, "`new` Operator, Dot Member Accessor, `;`"], +["new o.m(x);", 9, "`new` Operator, Dot Member Accessor, Function Call With 1 Argument, `;`"], +["new o.m(x,y);", 11, "``new` Operator, Dot Member Accessor, Function Call With 2 Arguments , `;`"], + +// Prefix and Postfix Increment. +["++x;", 3, "Prefix Increment, Identifier, `;`"], +["x++;", 3, "Identifier, Postfix Increment, `;`"], +["--x;", 3, "Prefix Decrement, Identifier, `;`"], +["x--;", 3, "Postfix Decrement, Identifier, `;`"], +["x ++;", 4, "Identifier, Space, Postfix Increment, `;`"], +["x /* comment */ ++;", 6, "Identifier, Block Comment, Postfix Increment, `;`"], +["++ /* comment */ x;", 6, "Prefix Increment, Block Comment, Identifier, `;`"], + +// Unary Operators. +["delete x;", 4, "`delete` Operator, Space, Identifier, `;`"], +["void x;", 4, "`void` Operator, Space, Identifier, `;`"], +["typeof x;", 4, "`typeof` Operator, Space, Identifier, `;`"], +["+x;", 3, "Unary `+` Operator, Identifier, `;`"], +["-x;", 3, "Unary Negation Operator, Identifier, `;`"], +["~x;", 3, "Bitwise NOT Operator, Identifier, `;`"], +["!x;", 3, "Logical NOT Operator, Identifier, `;`"], + +// Comma Operator. +["x, y;", 5, "Comma Operator"], + +// Miscellaneous. +["new Date++;", 5, "`new` Operator, Identifier, Postfix Increment, `;`"], +["+x++;", 4, "Unary `+`, Identifier, Postfix Increment, `;`"], + +// Expressions. +["1 * 2;", 6, "Integer, Multiplication, Integer, `;`"], +["1 / 2;", 6, "Integer, Division, Integer, `;`"], +["1 % 2;", 6, "Integer, Modulus, Integer, `;`"], +["1 + 2;", 6, "Integer, Addition, Integer, `;`"], +["1 - 2;", 6, "Integer, Subtraction, Integer, `;`"], +["1 << 2;", 6, "Integer, Bitwise Left Shift, Integer, `;`"], +["1 >>> 2;", 6, "Integer, Bitwise Zero-fill Right Shift, Integer, `;`"], +["1 >> 2;", 6, "Integer, Bitwise Sign-Propagating Right Shift, Integer, `;`"], +["1 * 2 + 3;", 10, "Order-of-Operations Expression, `;`"], +["(1+2)*3;", 8, "Parenthesized Additive Expression, Multiplication, `;`"], +["1*(2+3);", 8, "Multiplication, Parenthesized Additive Expression, `;`"], +["xy;", 4, "Greater-Than Relational Operator, `;`"], +["x<=y;", 4, "Less-Than-or-Equal-To Relational Operator, `;`"], +["x>=y;", 4, "Greater-Than-or-Equal-To Relational Operator, `;`"], +["x instanceof y;", 6, "`instanceof` Operator, `;`"], +["x in y;", 6, "`in` Operator, `;`"], +["x&y;", 4, "Bitwise AND Operator, `;`"], +["x^y;", 4, "Bitwise XOR Operator, `;`"], +["x|y;", 4, "Bitwise OR Operator, `;`"], +["x+y>>= y;", 6, "Bitwise Zero-Fill Right Shift Assignment, `;`"], +["x <<= y;", 6, "Bitwise Left Shift Assignment, `;`"], +["x += y;", 6, "Additive Assignment, `;`"], +["x -= y;", 6, "Subtractive Assignment, `;`"], +["x *= y;", 6, "Multiplicative Assignment, `;`"], +["x /= y;", 6, "Divisive Assignment, `;`"], +["x %= y;", 6, "Modulus Assignment, `;`"], +["x >>= y;", 6, "Bitwise Sign-Propagating Right Shift Assignment, `;`"], +["x &= y;", 6, "Bitwise AND Assignment, `;`"], +["x ^= y;", 6, "Bitwise XOR Assignment, `;`"], +["x |= y;", 6, "Bitwise OR Assignment, `;`"], + +// Blocks. +["{};", 3, "Empty Block, `;`"], +["{x;};", 5, "Block Containing 1 Identifier, `;`"], +["{x;y;};", 7, "Block Containing 2 Identifiers, `;`"], + +// Variable Declarations. +["var abc;", 4, "Variable Declaration"], +["var x,y;", 6, "Comma-Separated Variable Declarations, `;`"], +["var x=1,y=2;", 10, "Comma-Separated Variable Initializations, `;`"], +["var x,y=2;", 8, "Variable Declaration, Variable Initialization, `;`"], + +// Empty Statements. +[";", 1, "Empty Statement"], +["\n;", 2, "Linefeed, `;`"], + +// Expression Statements. +["x;", 2, "Identifier, `;`"], +["5;", 2, "Integer, `;`"], +["1+2;", 4, "Additive Statement, `;`"], + +// `if...else` Statements. +["if (c) x; else y;", 13, "Space-Delimited `if...else` Statement"], +["if (c) x;", 8, "Space-Delimited `if` Statement, `;`"], +["if (c) {} else {};", 14, "Empty Block-Delimited `if...else` Statement"], +["if (c1) if (c2) s1; else s2;", 19, "Nested `if...else` Statement Without Dangling `else`"], + +// `while` and `do...while` Loops. +["do s; while (e);", 11, "Space-Delimited `do...while` Loop"], +["do { s; } while (e);", 15, "Block-Delimited `do...while` Loop"], +["while (e) s;", 8, "Space-Delimited `while` Loop"], +["while (e) { s; };", 13, "Block-Delimited `while` Loop"], + +// `for` and `for...in` Loops. +["for (;;) ;", 8, "Infinite Space-Delimited `for` Loop"], +["for (;c;x++) x;", 12, "`for` Loop: Empty Initialization Condition; Space-Delimited Body"], +["for (i;i + + + +UglifyJS – a JavaScript parser/compressor/beautifier + + + + + + + + + + + + + +
+ +
+ +
+

UglifyJS – a JavaScript parser/compressor/beautifier

+ + + + +
+

1 UglifyJS — a JavaScript parser/compressor/beautifier

+
+ + +

+This package implements a general-purpose JavaScript +parser/compressor/beautifier toolkit. It is developed on NodeJS, but it +should work on any JavaScript platform supporting the CommonJS module system +(and if your platform of choice doesn't support CommonJS, you can easily +implement it, or discard the exports.* lines from UglifyJS sources). +

+

+The tokenizer/parser generates an abstract syntax tree from JS code. You +can then traverse the AST to learn more about the code, or do various +manipulations on it. This part is implemented in parse-js.js and it's a +port to JavaScript of the excellent parse-js Common Lisp library from Marijn Haverbeke. +

+

+( See cl-uglify-js if you're looking for the Common Lisp version of +UglifyJS. ) +

+

+The second part of this package, implemented in process.js, inspects and +manipulates the AST generated by the parser to provide the following: +

+
    +
  • ability to re-generate JavaScript code from the AST. Optionally + indented—you can use this if you want to “beautify” a program that has + been compressed, so that you can inspect the source. But you can also run + our code generator to print out an AST without any whitespace, so you + achieve compression as well. + +
  • +
  • shorten variable names (usually to single characters). Our mangler will + analyze the code and generate proper variable names, depending on scope + and usage, and is smart enough to deal with globals defined elsewhere, or + with eval() calls or with{} statements. In short, if eval() or + with{} are used in some scope, then all variables in that scope and any + variables in the parent scopes will remain unmangled, and any references + to such variables remain unmangled as well. + +
  • +
  • various small optimizations that may lead to faster code but certainly + lead to smaller code. Where possible, we do the following: + +
      +
    • foo["bar"] ==> foo.bar + +
    • +
    • remove block brackets {} + +
    • +
    • join consecutive var declarations: + var a = 10; var b = 20; ==> var a=10,b=20; + +
    • +
    • resolve simple constant expressions: 1 +2 * 3 ==> 7. We only do the + replacement if the result occupies less bytes; for example 1/3 would + translate to 0.333333333333, so in this case we don't replace it. + +
    • +
    • consecutive statements in blocks are merged into a sequence; in many + cases, this leaves blocks with a single statement, so then we can remove + the block brackets. + +
    • +
    • various optimizations for IF statements: + +
        +
      • if (foo) bar(); else baz(); ==> foo?bar():baz(); +
      • +
      • if (!foo) bar(); else baz(); ==> foo?baz():bar(); +
      • +
      • if (foo) bar(); ==> foo&&bar(); +
      • +
      • if (!foo) bar(); ==> foo||bar(); +
      • +
      • if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); +
      • +
      • if (foo) return bar(); else something(); ==> {if(foo)return bar();something()} + +
      • +
      + +
    • +
    • remove some unreachable code and warn about it (code that follows a + return, throw, break or continue statement, except + function/variable declarations). + +
    • +
    • act a limited version of a pre-processor (c.f. the pre-processor of + C/C++) to allow you to safely replace selected global symbols with + specified values. When combined with the optimisations above this can + make UglifyJS operate slightly more like a compilation process, in + that when certain symbols are replaced by constant values, entire code + blocks may be optimised away as unreachable. +
    • +
    + +
  • +
+ + + +
+ +
+

1.1 Unsafe transformations

+
+ + +

+The following transformations can in theory break code, although they're +probably safe in most practical cases. To enable them you need to pass the +--unsafe flag. +

+ +
+ +
+

1.1.1 Calls involving the global Array constructor

+
+ + +

+The following transformations occur: +

+ + + +
new Array(1, 2, 3, 4)  => [1,2,3,4]
+Array(a, b, c)         => [a,b,c]
+new Array(5)           => Array(5)
+new Array(a)           => Array(a)
+
+ + +

+These are all safe if the Array name isn't redefined. JavaScript does allow +one to globally redefine Array (and pretty much everything, in fact) but I +personally don't see why would anyone do that. +

+

+UglifyJS does handle the case where Array is redefined locally, or even +globally but with a function or var declaration. Therefore, in the +following cases UglifyJS doesn't touch calls or instantiations of Array: +

+ + + +
// case 1.  globally declared variable
+  var Array;
+  new Array(1, 2, 3);
+  Array(a, b);
+
+  // or (can be declared later)
+  new Array(1, 2, 3);
+  var Array;
+
+  // or (can be a function)
+  new Array(1, 2, 3);
+  function Array() { ... }
+
+// case 2.  declared in a function
+  (function(){
+    a = new Array(1, 2, 3);
+    b = Array(5, 6);
+    var Array;
+  })();
+
+  // or
+  (function(Array){
+    return Array(5, 6, 7);
+  })();
+
+  // or
+  (function(){
+    return new Array(1, 2, 3, 4);
+    function Array() { ... }
+  })();
+
+  // etc.
+
+ + +
+ +
+ +
+

1.1.2 obj.toString() ==> obj+“”

+
+ + +
+
+ +
+ +
+

1.2 Install (NPM)

+
+ + +

+UglifyJS is now available through NPM — npm install uglify-js should do +the job. +

+
+ +
+ +
+

1.3 Install latest code from GitHub

+
+ + + + + +
## clone the repository
+mkdir -p /where/you/wanna/put/it
+cd /where/you/wanna/put/it
+git clone git://github.com/mishoo/UglifyJS.git
+
+## make the module available to Node
+mkdir -p ~/.node_libraries/
+cd ~/.node_libraries/
+ln -s /where/you/wanna/put/it/UglifyJS/uglify-js.js
+
+## and if you want the CLI script too:
+mkdir -p ~/bin
+cd ~/bin
+ln -s /where/you/wanna/put/it/UglifyJS/bin/uglifyjs
+  # (then add ~/bin to your $PATH if it's not there already)
+
+ + +
+ +
+ +
+

1.4 Usage

+
+ + +

+There is a command-line tool that exposes the functionality of this library +for your shell-scripting needs: +

+ + + +
uglifyjs [ options... ] [ filename ]
+
+ + +

+filename should be the last argument and should name the file from which +to read the JavaScript code. If you don't specify it, it will read code +from STDIN. +

+

+Supported options: +

+
    +
  • -b or --beautify — output indented code; when passed, additional + options control the beautifier: + +
      +
    • -i N or --indent N — indentation level (number of spaces) + +
    • +
    • -q or --quote-keys — quote keys in literal objects (by default, + only keys that cannot be identifier names will be quotes). + +
    • +
    + +
  • +
  • --ascii — pass this argument to encode non-ASCII characters as + \uXXXX sequences. By default UglifyJS won't bother to do it and will + output Unicode characters instead. (the output is always encoded in UTF8, + but if you pass this option you'll only get ASCII). + +
  • +
  • -nm or --no-mangle — don't mangle names. + +
  • +
  • -nmf or --no-mangle-functions – in case you want to mangle variable + names, but not touch function names. + +
  • +
  • -ns or --no-squeeze — don't call ast_squeeze() (which does various + optimizations that result in smaller, less readable code). + +
  • +
  • -mt or --mangle-toplevel — mangle names in the toplevel scope too + (by default we don't do this). + +
  • +
  • --no-seqs — when ast_squeeze() is called (thus, unless you pass + --no-squeeze) it will reduce consecutive statements in blocks into a + sequence. For example, "a = 10; b = 20; foo();" will be written as + "a=10,b=20,foo();". In various occasions, this allows us to discard the + block brackets (since the block becomes a single statement). This is ON + by default because it seems safe and saves a few hundred bytes on some + libs that I tested it on, but pass --no-seqs to disable it. + +
  • +
  • --no-dead-code — by default, UglifyJS will remove code that is + obviously unreachable (code that follows a return, throw, break or + continue statement and is not a function/variable declaration). Pass + this option to disable this optimization. + +
  • +
  • -nc or --no-copyright — by default, uglifyjs will keep the initial + comment tokens in the generated code (assumed to be copyright information + etc.). If you pass this it will discard it. + +
  • +
  • -o filename or --output filename — put the result in filename. If + this isn't given, the result goes to standard output (or see next one). + +
  • +
  • --overwrite — if the code is read from a file (not from STDIN) and you + pass --overwrite then the output will be written in the same file. + +
  • +
  • --ast — pass this if you want to get the Abstract Syntax Tree instead + of JavaScript as output. Useful for debugging or learning more about the + internals. + +
  • +
  • -v or --verbose — output some notes on STDERR (for now just how long + each operation takes). + +
  • +
  • -d SYMBOL[=VALUE] or --define SYMBOL[=VALUE] — will replace + all instances of the specified symbol where used as an identifier + (except where symbol has properly declared by a var declaration or + use as function parameter or similar) with the specified value. This + argument may be specified multiple times to define multiple + symbols - if no value is specified the symbol will be replaced with + the value true, or you can specify a numeric value (such as + 1024), a quoted string value (such as ="object"= or + ='https://github.com'), or the name of another symbol or keyword (such as =null or document). + This allows you, for example, to assign meaningful names to key + constant values but discard the symbolic names in the uglified + version for brevity/efficiency, or when used wth care, allows + UglifyJS to operate as a form of conditional compilation + whereby defining appropriate values may, by dint of the constant + folding and dead code removal features above, remove entire + superfluous code blocks (e.g. completely remove instrumentation or + trace code for production use). + Where string values are being defined, the handling of quotes are + likely to be subject to the specifics of your command shell + environment, so you may need to experiment with quoting styles + depending on your platform, or you may find the option + --define-from-module more suitable for use. + +
  • +
  • -define-from-module SOMEMODULE — will load the named module (as + per the NodeJS require() function) and iterate all the exported + properties of the module defining them as symbol names to be defined + (as if by the --define option) per the name of each property + (i.e. without the module name prefix) and given the value of the + property. This is a much easier way to handle and document groups of + symbols to be defined rather than a large number of --define + options. + +
  • +
  • --unsafe — enable other additional optimizations that are known to be + unsafe in some contrived situations, but could still be generally useful. + For now only these: + +
      +
    • foo.toString() ==> foo+"" +
    • +
    • new Array(x,…) ==> [x,…] +
    • +
    • new Array(x) ==> Array(x) + +
    • +
    + +
  • +
  • --max-line-len (default 32K characters) — add a newline after around + 32K characters. I've seen both FF and Chrome croak when all the code was + on a single line of around 670K. Pass –max-line-len 0 to disable this + safety feature. + +
  • +
  • --reserved-names — some libraries rely on certain names to be used, as + pointed out in issue #92 and #81, so this option allow you to exclude such + names from the mangler. For example, to keep names require and $super + intact you'd specify –reserved-names "require,$super". + +
  • +
  • --inline-script – when you want to include the output literally in an + HTML <script> tag you can use this option to prevent </script from + showing up in the output. + +
  • +
  • --lift-vars – when you pass this, UglifyJS will apply the following + transformations (see the notes in API, ast_lift_variables): + +
      +
    • put all var declarations at the start of the scope +
    • +
    • make sure a variable is declared only once +
    • +
    • discard unused function arguments +
    • +
    • discard unused inner (named) functions +
    • +
    • finally, try to merge assignments into that one var declaration, if + possible. +
    • +
    + +
  • +
+ + + +
+ +
+

1.4.1 API

+
+ + +

+To use the library from JavaScript, you'd do the following (example for +NodeJS): +

+ + + +
var jsp = require("uglify-js").parser;
+var pro = require("uglify-js").uglify;
+
+var orig_code = "... JS code here";
+var ast = jsp.parse(orig_code); // parse code and get the initial AST
+ast = pro.ast_mangle(ast); // get a new AST with mangled names
+ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
+var final_code = pro.gen_code(ast); // compressed code here
+
+ + +

+The above performs the full compression that is possible right now. As you +can see, there are a sequence of steps which you can apply. For example if +you want compressed output but for some reason you don't want to mangle +variable names, you would simply skip the line that calls +pro.ast_mangle(ast). +

+

+Some of these functions take optional arguments. Here's a description: +

+
    +
  • jsp.parse(code, strict_semicolons) – parses JS code and returns an AST. + strict_semicolons is optional and defaults to false. If you pass + true then the parser will throw an error when it expects a semicolon and + it doesn't find it. For most JS code you don't want that, but it's useful + if you want to strictly sanitize your code. + +
  • +
  • pro.ast_lift_variables(ast) – merge and move var declarations to the + scop of the scope; discard unused function arguments or variables; discard + unused (named) inner functions. It also tries to merge assignments + following the var declaration into it. + +

    + If your code is very hand-optimized concerning var declarations, this + lifting variable declarations might actually increase size. For me it + helps out. On jQuery it adds 865 bytes (243 after gzip). YMMV. Also + note that (since it's not enabled by default) this operation isn't yet + heavily tested (please report if you find issues!). +

    +

    + Note that although it might increase the image size (on jQuery it gains + 865 bytes, 243 after gzip) it's technically more correct: in certain + situations, dead code removal might drop variable declarations, which + would not happen if the variables are lifted in advance. +

    +

    + Here's an example of what it does: +

  • +
+ + + + + +
function f(a, b, c, d, e) {
+    var q;
+    var w;
+    w = 10;
+    q = 20;
+    for (var i = 1; i < 10; ++i) {
+        var boo = foo(a);
+    }
+    for (var i = 0; i < 1; ++i) {
+        var boo = bar(c);
+    }
+    function foo(){ ... }
+    function bar(){ ... }
+    function baz(){ ... }
+}
+
+// transforms into ==>
+
+function f(a, b, c) {
+    var i, boo, w = 10, q = 20;
+    for (i = 1; i < 10; ++i) {
+        boo = foo(a);
+    }
+    for (i = 0; i < 1; ++i) {
+        boo = bar(c);
+    }
+    function foo() { ... }
+    function bar() { ... }
+}
+
+ + +
    +
  • pro.ast_mangle(ast, options) – generates a new AST containing mangled + (compressed) variable and function names. It supports the following + options: + +
      +
    • toplevel – mangle toplevel names (by default we don't touch them). +
    • +
    • except – an array of names to exclude from compression. +
    • +
    • defines – an object with properties named after symbols to + replace (see the --define option for the script) and the values + representing the AST replacement value. + +
    • +
    + +
  • +
  • pro.ast_squeeze(ast, options) – employs further optimizations designed + to reduce the size of the code that gen_code would generate from the + AST. Returns a new AST. options can be a hash; the supported options + are: + +
      +
    • make_seqs (default true) which will cause consecutive statements in a + block to be merged using the "sequence" (comma) operator + +
    • +
    • dead_code (default true) which will remove unreachable code. + +
    • +
    + +
  • +
  • pro.gen_code(ast, options) – generates JS code from the AST. By + default it's minified, but using the options argument you can get nicely + formatted output. options is, well, optional :-) and if you pass it it + must be an object and supports the following properties (below you can see + the default values): + +
      +
    • beautify: false – pass true if you want indented output +
    • +
    • indent_start: 0 (only applies when beautify is true) – initial + indentation in spaces +
    • +
    • indent_level: 4 (only applies when beautify is true) -- + indentation level, in spaces (pass an even number) +
    • +
    • quote_keys: false – if you pass true it will quote all keys in + literal objects +
    • +
    • space_colon: false (only applies when beautify is true) – wether + to put a space before the colon in object literals +
    • +
    • ascii_only: false – pass true if you want to encode non-ASCII + characters as \uXXXX. +
    • +
    • inline_script: false – pass true to escape occurrences of + </script in strings +
    • +
    + +
  • +
+ + +
+ +
+ +
+

1.4.2 Beautifier shortcoming – no more comments

+
+ + +

+The beautifier can be used as a general purpose indentation tool. It's +useful when you want to make a minified file readable. One limitation, +though, is that it discards all comments, so you don't really want to use it +to reformat your code, unless you don't have, or don't care about, comments. +

+

+In fact it's not the beautifier who discards comments — they are dumped at +the parsing stage, when we build the initial AST. Comments don't really +make sense in the AST, and while we could add nodes for them, it would be +inconvenient because we'd have to add special rules to ignore them at all +the processing stages. +

+
+ +
+ +
+

1.4.3 Use as a code pre-processor

+
+ + +

+The --define option can be used, particularly when combined with the +constant folding logic, as a form of pre-processor to enable or remove +particular constructions, such as might be used for instrumenting +development code, or to produce variations aimed at a specific +platform. +

+

+The code below illustrates the way this can be done, and how the +symbol replacement is performed. +

+ + + +
CLAUSE1: if (typeof DEVMODE === 'undefined') {
+    DEVMODE = true;
+}
+
+CLAUSE2: function init() {
+    if (DEVMODE) {
+        console.log("init() called");
+    }
+    ....
+    DEVMODE &amp;&amp; console.log("init() complete");
+}
+
+CLAUSE3: function reportDeviceStatus(device) {
+    var DEVMODE = device.mode, DEVNAME = device.name;
+    if (DEVMODE === 'open') {
+        ....
+    }
+}
+
+ + +

+When the above code is normally executed, the undeclared global +variable DEVMODE will be assigned the value true (see CLAUSE1) +and so the init() function (CLAUSE2) will write messages to the +console log when executed, but in CLAUSE3 a locally declared +variable will mask access to the DEVMODE global symbol. +

+

+If the above code is processed by UglifyJS with an argument of +--define DEVMODE=false then UglifyJS will replace DEVMODE with the +boolean constant value false within CLAUSE1 and CLAUSE2, but it +will leave CLAUSE3 as it stands because there DEVMODE resolves to +a validly declared variable. +

+

+And more so, the constant-folding features of UglifyJS will recognise +that the if condition of CLAUSE1 is thus always false, and so will +remove the test and body of CLAUSE1 altogether (including the +otherwise slightly problematical statement false = true; which it +will have formed by replacing DEVMODE in the body). Similarly, +within CLAUSE2 both calls to console.log() will be removed +altogether. +

+

+In this way you can mimic, to a limited degree, the functionality of +the C/C++ pre-processor to enable or completely remove blocks +depending on how certain symbols are defined - perhaps using UglifyJS +to generate different versions of source aimed at different +environments +

+

+It is recommmended (but not made mandatory) that symbols designed for +this purpose are given names consisting of UPPER_CASE_LETTERS to +distinguish them from other (normal) symbols and avoid the sort of +clash that CLAUSE3 above illustrates. +

+
+
+ +
+ +
+

1.5 Compression – how good is it?

+
+ + +

+Here are updated statistics. (I also updated my Google Closure and YUI +installations). +

+

+We're still a lot better than YUI in terms of compression, though slightly +slower. We're still a lot faster than Closure, and compression after gzip +is comparable. +

+ + ++ + + + + + + + + + +
FileUglifyJSUglifyJS+gzipClosureClosure+gzipYUIYUI+gzip
jquery-1.6.2.js91001 (0:01.59)3189690678 (0:07.40)31979101527 (0:01.82)34646
paper.js142023 (0:01.65)43334134301 (0:07.42)42495173383 (0:01.58)48785
prototype.js88544 (0:01.09)2668086955 (0:06.97)2632692130 (0:00.79)28624
thelib-full.js (DynarchLIB)251939 (0:02.55)72535249911 (0:09.05)72696258869 (0:01.94)76584
+ + +
+ +
+ +
+

1.6 Bugs?

+
+ + +

+Unfortunately, for the time being there is no automated test suite. But I +ran the compressor manually on non-trivial code, and then I tested that the +generated code works as expected. A few hundred times. +

+

+DynarchLIB was started in times when there was no good JS minifier. +Therefore I was quite religious about trying to write short code manually, +and as such DL contains a lot of syntactic hacks1 such as “foo == bar ? a += 10 : b = 20”, though the more readable version would clearly be to use +“if/else”. +

+

+Since the parser/compressor runs fine on DL and jQuery, I'm quite confident +that it's solid enough for production use. If you can identify any bugs, +I'd love to hear about them (use the Google Group or email me directly). +

+
+ +
+ +
+

1.7 Links

+
+ + + + + +
+ +
+ +
+

1.8 License

+
+ + +

+UglifyJS is released under the BSD license: +

+ + + +
Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    * Redistributions of source code must retain the above
+      copyright notice, this list of conditions and the following
+      disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+ + +
+

Footnotes:

+
+

1 I even reported a few bugs and suggested some fixes in the original + parse-js library, and Marijn pushed fixes literally in minutes. +

+
+
+ +
+
+
+ +
+

Date: 2011-12-09 14:59:08 EET

+

Author: Mihai Bazon

+

Org version 7.7 with Emacs version 23

+Validate XHTML 1.0 + +
+ + diff --git a/node_modules/socket.io-client/node_modules/uglify-js/README.org b/node_modules/socket.io-client/node_modules/uglify-js/README.org new file mode 100644 index 0000000..4d01fdf --- /dev/null +++ b/node_modules/socket.io-client/node_modules/uglify-js/README.org @@ -0,0 +1,574 @@ +#+TITLE: UglifyJS -- a JavaScript parser/compressor/beautifier +#+KEYWORDS: javascript, js, parser, compiler, compressor, mangle, minify, minifier +#+DESCRIPTION: a JavaScript parser/compressor/beautifier in JavaScript +#+STYLE: +#+AUTHOR: Mihai Bazon +#+EMAIL: mihai.bazon@gmail.com + +* UglifyJS --- a JavaScript parser/compressor/beautifier + +This package implements a general-purpose JavaScript +parser/compressor/beautifier toolkit. It is developed on [[http://nodejs.org/][NodeJS]], but it +should work on any JavaScript platform supporting the CommonJS module system +(and if your platform of choice doesn't support CommonJS, you can easily +implement it, or discard the =exports.*= lines from UglifyJS sources). + +The tokenizer/parser generates an abstract syntax tree from JS code. You +can then traverse the AST to learn more about the code, or do various +manipulations on it. This part is implemented in [[../lib/parse-js.js][parse-js.js]] and it's a +port to JavaScript of the excellent [[http://marijn.haverbeke.nl/parse-js/][parse-js]] Common Lisp library from [[http://marijn.haverbeke.nl/][Marijn +Haverbeke]]. + +( See [[http://github.com/mishoo/cl-uglify-js][cl-uglify-js]] if you're looking for the Common Lisp version of +UglifyJS. ) + +The second part of this package, implemented in [[../lib/process.js][process.js]], inspects and +manipulates the AST generated by the parser to provide the following: + +- ability to re-generate JavaScript code from the AST. Optionally + indented---you can use this if you want to “beautify” a program that has + been compressed, so that you can inspect the source. But you can also run + our code generator to print out an AST without any whitespace, so you + achieve compression as well. + +- shorten variable names (usually to single characters). Our mangler will + analyze the code and generate proper variable names, depending on scope + and usage, and is smart enough to deal with globals defined elsewhere, or + with =eval()= calls or =with{}= statements. In short, if =eval()= or + =with{}= are used in some scope, then all variables in that scope and any + variables in the parent scopes will remain unmangled, and any references + to such variables remain unmangled as well. + +- various small optimizations that may lead to faster code but certainly + lead to smaller code. Where possible, we do the following: + + - foo["bar"] ==> foo.bar + + - remove block brackets ={}= + + - join consecutive var declarations: + var a = 10; var b = 20; ==> var a=10,b=20; + + - resolve simple constant expressions: 1 +2 * 3 ==> 7. We only do the + replacement if the result occupies less bytes; for example 1/3 would + translate to 0.333333333333, so in this case we don't replace it. + + - consecutive statements in blocks are merged into a sequence; in many + cases, this leaves blocks with a single statement, so then we can remove + the block brackets. + + - various optimizations for IF statements: + + - if (foo) bar(); else baz(); ==> foo?bar():baz(); + - if (!foo) bar(); else baz(); ==> foo?baz():bar(); + - if (foo) bar(); ==> foo&&bar(); + - if (!foo) bar(); ==> foo||bar(); + - if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); + - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()} + + - remove some unreachable code and warn about it (code that follows a + =return=, =throw=, =break= or =continue= statement, except + function/variable declarations). + + - act a limited version of a pre-processor (c.f. the pre-processor of + C/C++) to allow you to safely replace selected global symbols with + specified values. When combined with the optimisations above this can + make UglifyJS operate slightly more like a compilation process, in + that when certain symbols are replaced by constant values, entire code + blocks may be optimised away as unreachable. + +** <> + +The following transformations can in theory break code, although they're +probably safe in most practical cases. To enable them you need to pass the +=--unsafe= flag. + +*** Calls involving the global Array constructor + +The following transformations occur: + +#+BEGIN_SRC js +new Array(1, 2, 3, 4) => [1,2,3,4] +Array(a, b, c) => [a,b,c] +new Array(5) => Array(5) +new Array(a) => Array(a) +#+END_SRC + +These are all safe if the Array name isn't redefined. JavaScript does allow +one to globally redefine Array (and pretty much everything, in fact) but I +personally don't see why would anyone do that. + +UglifyJS does handle the case where Array is redefined locally, or even +globally but with a =function= or =var= declaration. Therefore, in the +following cases UglifyJS *doesn't touch* calls or instantiations of Array: + +#+BEGIN_SRC js +// case 1. globally declared variable + var Array; + new Array(1, 2, 3); + Array(a, b); + + // or (can be declared later) + new Array(1, 2, 3); + var Array; + + // or (can be a function) + new Array(1, 2, 3); + function Array() { ... } + +// case 2. declared in a function + (function(){ + a = new Array(1, 2, 3); + b = Array(5, 6); + var Array; + })(); + + // or + (function(Array){ + return Array(5, 6, 7); + })(); + + // or + (function(){ + return new Array(1, 2, 3, 4); + function Array() { ... } + })(); + + // etc. +#+END_SRC + +*** =obj.toString()= ==> =obj+“”= + +** Install (NPM) + +UglifyJS is now available through NPM --- =npm install uglify-js= should do +the job. + +** Install latest code from GitHub + +#+BEGIN_SRC sh +## clone the repository +mkdir -p /where/you/wanna/put/it +cd /where/you/wanna/put/it +git clone git://github.com/mishoo/UglifyJS.git + +## make the module available to Node +mkdir -p ~/.node_libraries/ +cd ~/.node_libraries/ +ln -s /where/you/wanna/put/it/UglifyJS/uglify-js.js + +## and if you want the CLI script too: +mkdir -p ~/bin +cd ~/bin +ln -s /where/you/wanna/put/it/UglifyJS/bin/uglifyjs + # (then add ~/bin to your $PATH if it's not there already) +#+END_SRC + +** Usage + +There is a command-line tool that exposes the functionality of this library +for your shell-scripting needs: + +#+BEGIN_SRC sh +uglifyjs [ options... ] [ filename ] +#+END_SRC + +=filename= should be the last argument and should name the file from which +to read the JavaScript code. If you don't specify it, it will read code +from STDIN. + +Supported options: + +- =-b= or =--beautify= --- output indented code; when passed, additional + options control the beautifier: + + - =-i N= or =--indent N= --- indentation level (number of spaces) + + - =-q= or =--quote-keys= --- quote keys in literal objects (by default, + only keys that cannot be identifier names will be quotes). + +- =--ascii= --- pass this argument to encode non-ASCII characters as + =\uXXXX= sequences. By default UglifyJS won't bother to do it and will + output Unicode characters instead. (the output is always encoded in UTF8, + but if you pass this option you'll only get ASCII). + +- =-nm= or =--no-mangle= --- don't mangle names. + +- =-nmf= or =--no-mangle-functions= -- in case you want to mangle variable + names, but not touch function names. + +- =-ns= or =--no-squeeze= --- don't call =ast_squeeze()= (which does various + optimizations that result in smaller, less readable code). + +- =-mt= or =--mangle-toplevel= --- mangle names in the toplevel scope too + (by default we don't do this). + +- =--no-seqs= --- when =ast_squeeze()= is called (thus, unless you pass + =--no-squeeze=) it will reduce consecutive statements in blocks into a + sequence. For example, "a = 10; b = 20; foo();" will be written as + "a=10,b=20,foo();". In various occasions, this allows us to discard the + block brackets (since the block becomes a single statement). This is ON + by default because it seems safe and saves a few hundred bytes on some + libs that I tested it on, but pass =--no-seqs= to disable it. + +- =--no-dead-code= --- by default, UglifyJS will remove code that is + obviously unreachable (code that follows a =return=, =throw=, =break= or + =continue= statement and is not a function/variable declaration). Pass + this option to disable this optimization. + +- =-nc= or =--no-copyright= --- by default, =uglifyjs= will keep the initial + comment tokens in the generated code (assumed to be copyright information + etc.). If you pass this it will discard it. + +- =-o filename= or =--output filename= --- put the result in =filename=. If + this isn't given, the result goes to standard output (or see next one). + +- =--overwrite= --- if the code is read from a file (not from STDIN) and you + pass =--overwrite= then the output will be written in the same file. + +- =--ast= --- pass this if you want to get the Abstract Syntax Tree instead + of JavaScript as output. Useful for debugging or learning more about the + internals. + +- =-v= or =--verbose= --- output some notes on STDERR (for now just how long + each operation takes). + +- =-d SYMBOL[=VALUE]= or =--define SYMBOL[=VALUE]= --- will replace + all instances of the specified symbol where used as an identifier + (except where symbol has properly declared by a var declaration or + use as function parameter or similar) with the specified value. This + argument may be specified multiple times to define multiple + symbols - if no value is specified the symbol will be replaced with + the value =true=, or you can specify a numeric value (such as + =1024=), a quoted string value (such as ="object"= or + ='https://github.com'=), or the name of another symbol or keyword + (such as =null= or =document=). + This allows you, for example, to assign meaningful names to key + constant values but discard the symbolic names in the uglified + version for brevity/efficiency, or when used wth care, allows + UglifyJS to operate as a form of *conditional compilation* + whereby defining appropriate values may, by dint of the constant + folding and dead code removal features above, remove entire + superfluous code blocks (e.g. completely remove instrumentation or + trace code for production use). + Where string values are being defined, the handling of quotes are + likely to be subject to the specifics of your command shell + environment, so you may need to experiment with quoting styles + depending on your platform, or you may find the option + =--define-from-module= more suitable for use. + +- =-define-from-module SOMEMODULE= --- will load the named module (as + per the NodeJS =require()= function) and iterate all the exported + properties of the module defining them as symbol names to be defined + (as if by the =--define= option) per the name of each property + (i.e. without the module name prefix) and given the value of the + property. This is a much easier way to handle and document groups of + symbols to be defined rather than a large number of =--define= + options. + +- =--unsafe= --- enable other additional optimizations that are known to be + unsafe in some contrived situations, but could still be generally useful. + For now only these: + + - foo.toString() ==> foo+"" + - new Array(x,...) ==> [x,...] + - new Array(x) ==> Array(x) + +- =--max-line-len= (default 32K characters) --- add a newline after around + 32K characters. I've seen both FF and Chrome croak when all the code was + on a single line of around 670K. Pass --max-line-len 0 to disable this + safety feature. + +- =--reserved-names= --- some libraries rely on certain names to be used, as + pointed out in issue #92 and #81, so this option allow you to exclude such + names from the mangler. For example, to keep names =require= and =$super= + intact you'd specify --reserved-names "require,$super". + +- =--inline-script= -- when you want to include the output literally in an + HTML = + + + */ + +(function() { + this.loggly = function(opts) { + this.user_agent = get_agent(); + this.browser_size = get_size(); + log_methods = {'error': 5, 'warn': 4, 'info': 3, 'debug': 2, 'log': 1}; + if (!opts.url) throw new Error("Please include a Loggly HTTP URL."); + if (!opts.level) { + this.level = log_methods['info']; + } else { + this.level = log_methods[opts.level]; + } + this.log = function(data) { + if (log_methods['log'] == this.level) { + opts.data = data; + janky(opts); + } + }; + this.debug = function(data) { + if (log_methods['debug'] >= this.level) { + opts.data = data; + janky(opts); + } + }; + this.info = function(data) { + if (log_methods['info'] >= this.level) { + opts.data = data; + janky(opts); + } + }; + this.warn = function(data) { + if (log_methods['warn'] >= this.level) { + opts.data = data; + janky(opts); + } + }; + this.error = function(data) { + if (log_methods['error'] >= this.level) { + opts.data = data; + janky(opts); + } + }; + }; + this.janky = function(opts) { + janky._form(function(iframe, form) { + form.setAttribute("action", opts.url); + form.setAttribute("method", "post"); + janky._input(iframe, form, opts.data); + form.submit(); + setTimeout(function(){ + document.body.removeChild(iframe); + }, 2000); + }); + }; + this.janky._form = function(cb) { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + iframe.style.display = "none"; + setTimeout(function() { + var form = iframe.contentWindow.document.createElement("form"); + iframe.contentWindow.document.body.appendChild(form); + cb(iframe, form); + }, 0); + }; + this.janky._input = function(iframe, form, data) { + var inp = iframe.contentWindow.document.createElement("input"); + inp.setAttribute("type", "hidden"); + inp.setAttribute("name", "source"); + inp.value = "castor " + data; + form.appendChild(inp); + }; + this.get_agent = function () { + return navigator.appCodeName + navigator.appName + navigator.appVersion; + }; + this.get_size = function () { + var width = 0; var height = 0; + if( typeof( window.innerWidth ) == 'number' ) { + width = window.innerWidth; height = window.innerHeight; + } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) { + width = document.documentElement.clientWidth; height = document.documentElement.clientHeight; + } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) { + width = document.body.clientWidth; height = document.body.clientHeight; + } + return {'height': height, 'width': width}; + }; +})(); + + +jsworld={};jsworld.formatIsoDateTime=function(a,b){if(typeof a==="undefined")a=new Date;if(typeof b==="undefined")b=false;var c=jsworld.formatIsoDate(a)+" "+jsworld.formatIsoTime(a);if(b){var d=a.getHours()-a.getUTCHours();var e=Math.abs(d);var f=a.getUTCMinutes();var g=a.getMinutes();if(g!=f&&f<30&&d<0)e--;if(g!=f&&f>30&&d>0)e--;var h;if(g!=f)h=":30";else h=":00";var i;if(e<10)i="0"+e+h;else i=""+e+h;if(d<0)i="-"+i;else i="+"+i;c=c+i}return c};jsworld.formatIsoDate=function(a){if(typeof a==="undefined")a=new Date;var b=a.getFullYear();var c=a.getMonth()+1;var d=a.getDate();return b+"-"+jsworld._zeroPad(c,2)+"-"+jsworld._zeroPad(d,2)};jsworld.formatIsoTime=function(a){if(typeof a==="undefined")a=new Date;var b=a.getHours();var c=a.getMinutes();var d=a.getSeconds();return jsworld._zeroPad(b,2)+":"+jsworld._zeroPad(c,2)+":"+jsworld._zeroPad(d,2)};jsworld.parseIsoDateTime=function(a){if(typeof a!="string")throw"Error: The parameter must be a string";var b=a.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)/);if(b===null)b=a.match(/^(\d\d\d\d)(\d\d)(\d\d)[T ](\d\d)(\d\d)(\d\d)/);if(b===null)b=a.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d)(\d\d)(\d\d)/);if(b===null)b=a.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)/);if(b===null)throw"Error: Invalid ISO-8601 date/time string";var c=parseInt(b[1],10);var d=parseInt(b[2],10);var e=parseInt(b[3],10);var f=parseInt(b[4],10);var g=parseInt(b[5],10);var h=parseInt(b[6],10);if(d<1||d>12||e<1||e>31||f<0||f>23||g<0||g>59||h<0||h>59)throw"Error: Invalid ISO-8601 date/time value";var i=new Date(c,d-1,e,f,g,h);if(i.getDate()!=e||i.getMonth()+1!=d)throw"Error: Invalid date";return i};jsworld.parseIsoDate=function(a){if(typeof a!="string")throw"Error: The parameter must be a string";var b=a.match(/^(\d\d\d\d)-(\d\d)-(\d\d)/);if(b===null)b=a.match(/^(\d\d\d\d)(\d\d)(\d\d)/);if(b===null)throw"Error: Invalid ISO-8601 date string";var c=parseInt(b[1],10);var d=parseInt(b[2],10);var e=parseInt(b[3],10);if(d<1||d>12||e<1||e>31)throw"Error: Invalid ISO-8601 date value";var f=new Date(c,d-1,e);if(f.getDate()!=e||f.getMonth()+1!=d)throw"Error: Invalid date";return f};jsworld.parseIsoTime=function(a){if(typeof a!="string")throw"Error: The parameter must be a string";var b=a.match(/^(\d\d):(\d\d):(\d\d)/);if(b===null)b=a.match(/^(\d\d)(\d\d)(\d\d)/);if(b===null)throw"Error: Invalid ISO-8601 date/time string";var c=parseInt(b[1],10);var d=parseInt(b[2],10);var e=parseInt(b[3],10);if(c<0||c>23||d<0||d>59||e<0||e>59)throw"Error: Invalid ISO-8601 time value";return new Date(0,0,0,c,d,e)};jsworld._trim=function(a){var b=" \n\r\t\f \u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000";for(var c=0;c=0;c--){if(b.indexOf(a.charAt(c))===-1){a=a.substring(0,c+1);break}}return b.indexOf(a.charAt(0))===-1?a:""};jsworld._isNumber=function(a){if(typeof a=="number")return true;if(typeof a!="string")return false;var b=a+"";return/^-?(\d+|\d*\.\d+)$/.test(b)};jsworld._isInteger=function(a){if(typeof a!="number"&&typeof a!="string")return false;var b=a+"";return/^-?\d+$/.test(b)};jsworld._isFloat=function(a){if(typeof a!="number"&&typeof a!="string")return false;var b=a+"";return/^-?\.\d+?$/.test(b)};jsworld._hasOption=function(a,b){if(typeof a!="string"||typeof b!="string")return false;if(b.indexOf(a)!=-1)return true;else return false};jsworld._stringReplaceAll=function(a,b,c){var d;if(b.length==1&&c.length==1){d="";for(var e=0;e0){if(d.length>0)g=parseInt(d.shift(),10);if(isNaN(g))throw"Error: Invalid grouping";if(g==-1){e=a.substring(0,f)+e;break}f-=g;if(f<1){e=a.substring(0,f+g)+e;break}e=c+a.substring(f,f+g)+e}return e};jsworld._formatFractionPart=function(a,b){for(var c=0;a.length0)return a;else throw"Empty or no string"};if(a==null||typeof a!="object")throw"Error: Invalid/missing locale properties";if(typeof a.decimal_point!="string")throw"Error: Invalid/missing decimal_point property";this.decimal_point=a.decimal_point;if(typeof a.thousands_sep!="string")throw"Error: Invalid/missing thousands_sep property";this.thousands_sep=a.thousands_sep;if(typeof a.grouping!="string")throw"Error: Invalid/missing grouping property";this.grouping=a.grouping;if(typeof a.int_curr_symbol!="string")throw"Error: Invalid/missing int_curr_symbol property";if(!/[A-Za-z]{3}.?/.test(a.int_curr_symbol))throw"Error: Invalid int_curr_symbol property";this.int_curr_symbol=a.int_curr_symbol;if(typeof a.currency_symbol!="string")throw"Error: Invalid/missing currency_symbol property";this.currency_symbol=a.currency_symbol;if(typeof a.frac_digits!="number"&&a.frac_digits<0)throw"Error: Invalid/missing frac_digits property";this.frac_digits=a.frac_digits;if(a.mon_decimal_point===null||a.mon_decimal_point==""){if(this.frac_digits>0)throw"Error: Undefined mon_decimal_point property";else a.mon_decimal_point=""}if(typeof a.mon_decimal_point!="string")throw"Error: Invalid/missing mon_decimal_point property";this.mon_decimal_point=a.mon_decimal_point;if(typeof a.mon_thousands_sep!="string")throw"Error: Invalid/missing mon_thousands_sep property";this.mon_thousands_sep=a.mon_thousands_sep;if(typeof a.mon_grouping!="string")throw"Error: Invalid/missing mon_grouping property";this.mon_grouping=a.mon_grouping;if(typeof a.positive_sign!="string")throw"Error: Invalid/missing positive_sign property";this.positive_sign=a.positive_sign;if(typeof a.negative_sign!="string")throw"Error: Invalid/missing negative_sign property";this.negative_sign=a.negative_sign;if(a.p_cs_precedes!==0&&a.p_cs_precedes!==1)throw"Error: Invalid/missing p_cs_precedes property, must be 0 or 1";this.p_cs_precedes=a.p_cs_precedes;if(a.n_cs_precedes!==0&&a.n_cs_precedes!==1)throw"Error: Invalid/missing n_cs_precedes, must be 0 or 1";this.n_cs_precedes=a.n_cs_precedes;if(a.p_sep_by_space!==0&&a.p_sep_by_space!==1&&a.p_sep_by_space!==2)throw"Error: Invalid/missing p_sep_by_space property, must be 0, 1 or 2";this.p_sep_by_space=a.p_sep_by_space;if(a.n_sep_by_space!==0&&a.n_sep_by_space!==1&&a.n_sep_by_space!==2)throw"Error: Invalid/missing n_sep_by_space property, must be 0, 1, or 2";this.n_sep_by_space=a.n_sep_by_space;if(a.p_sign_posn!==0&&a.p_sign_posn!==1&&a.p_sign_posn!==2&&a.p_sign_posn!==3&&a.p_sign_posn!==4)throw"Error: Invalid/missing p_sign_posn property, must be 0, 1, 2, 3 or 4";this.p_sign_posn=a.p_sign_posn;if(a.n_sign_posn!==0&&a.n_sign_posn!==1&&a.n_sign_posn!==2&&a.n_sign_posn!==3&&a.n_sign_posn!==4)throw"Error: Invalid/missing n_sign_posn property, must be 0, 1, 2, 3 or 4";this.n_sign_posn=a.n_sign_posn;if(typeof a.int_frac_digits!="number"&&a.int_frac_digits<0)throw"Error: Invalid/missing int_frac_digits property";this.int_frac_digits=a.int_frac_digits;if(a.int_p_cs_precedes!==0&&a.int_p_cs_precedes!==1)throw"Error: Invalid/missing int_p_cs_precedes property, must be 0 or 1";this.int_p_cs_precedes=a.int_p_cs_precedes;if(a.int_n_cs_precedes!==0&&a.int_n_cs_precedes!==1)throw"Error: Invalid/missing int_n_cs_precedes property, must be 0 or 1";this.int_n_cs_precedes=a.int_n_cs_precedes;if(a.int_p_sep_by_space!==0&&a.int_p_sep_by_space!==1&&a.int_p_sep_by_space!==2)throw"Error: Invalid/missing int_p_sep_by_spacev, must be 0, 1 or 2";this.int_p_sep_by_space=a.int_p_sep_by_space;if(a.int_n_sep_by_space!==0&&a.int_n_sep_by_space!==1&&a.int_n_sep_by_space!==2)throw"Error: Invalid/missing int_n_sep_by_space property, must be 0, 1, or 2";this.int_n_sep_by_space=a.int_n_sep_by_space;if(a.int_p_sign_posn!==0&&a.int_p_sign_posn!==1&&a.int_p_sign_posn!==2&&a.int_p_sign_posn!==3&&a.int_p_sign_posn!==4)throw"Error: Invalid/missing int_p_sign_posn property, must be 0, 1, 2, 3 or 4";this.int_p_sign_posn=a.int_p_sign_posn;if(a.int_n_sign_posn!==0&&a.int_n_sign_posn!==1&&a.int_n_sign_posn!==2&&a.int_n_sign_posn!==3&&a.int_n_sign_posn!==4)throw"Error: Invalid/missing int_n_sign_posn property, must be 0, 1, 2, 3 or 4";this.int_n_sign_posn=a.int_n_sign_posn;if(a==null||typeof a!="object")throw"Error: Invalid/missing time locale properties";try{this.abday=this._parseList(a.abday,7)}catch(b){throw"Error: Invalid abday property: "+b}try{this.day=this._parseList(a.day,7)}catch(b){throw"Error: Invalid day property: "+b}try{this.abmon=this._parseList(a.abmon,12)}catch(b){throw"Error: Invalid abmon property: "+b}try{this.mon=this._parseList(a.mon,12)}catch(b){throw"Error: Invalid mon property: "+b}try{this.d_fmt=this._validateFormatString(a.d_fmt)}catch(b){throw"Error: Invalid d_fmt property: "+b}try{this.t_fmt=this._validateFormatString(a.t_fmt)}catch(b){throw"Error: Invalid t_fmt property: "+b}try{this.d_t_fmt=this._validateFormatString(a.d_t_fmt)}catch(b){throw"Error: Invalid d_t_fmt property: "+b}try{var c=this._parseList(a.am_pm,2);this.am=c[0];this.pm=c[1]}catch(b){this.am="";this.pm=""}this.getAbbreviatedWeekdayName=function(a){if(typeof a=="undefined"||a===null)return this.abday;if(!jsworld._isInteger(a)||a<0||a>6)throw"Error: Invalid weekday argument, must be an integer [0..6]";return this.abday[a]};this.getWeekdayName=function(a){if(typeof a=="undefined"||a===null)return this.day;if(!jsworld._isInteger(a)||a<0||a>6)throw"Error: Invalid weekday argument, must be an integer [0..6]";return this.day[a]};this.getAbbreviatedMonthName=function(a){if(typeof a=="undefined"||a===null)return this.abmon;if(!jsworld._isInteger(a)||a<0||a>11)throw"Error: Invalid month argument, must be an integer [0..11]";return this.abmon[a]};this.getMonthName=function(a){if(typeof a=="undefined"||a===null)return this.mon;if(!jsworld._isInteger(a)||a<0||a>11)throw"Error: Invalid month argument, must be an integer [0..11]";return this.mon[a]};this.getDecimalPoint=function(){return this.decimal_point};this.getCurrencySymbol=function(){return this.currency_symbol};this.getIntCurrencySymbol=function(){return this.int_curr_symbol.substring(0,3)};this.currencySymbolPrecedes=function(){if(this.p_cs_precedes==1)return true;else return false};this.intCurrencySymbolPrecedes=function(){if(this.int_p_cs_precedes==1)return true;else return false};this.getMonetaryDecimalPoint=function(){return this.mon_decimal_point};this.getFractionalDigits=function(){return this.frac_digits};this.getIntFractionalDigits=function(){return this.int_frac_digits}};jsworld.NumericFormatter=function(a){if(typeof a!="object"||a._className!="jsworld.Locale")throw"Constructor error: You must provide a valid jsworld.Locale instance";this.lc=a;this.format=function(a,b){if(typeof a=="string")a=jsworld._trim(a);if(!jsworld._isNumber(a))throw"Error: The input is not a number";var c=parseFloat(a,10);var d=jsworld._getPrecision(b);if(d!=-1)c=Math.round(c*Math.pow(10,d))/Math.pow(10,d);var e=jsworld._splitNumber(String(c));var f;if(c===0)f="0";else f=jsworld._hasOption("^",b)?e.integer:jsworld._formatIntegerPart(e.integer,this.lc.grouping,this.lc.thousands_sep);var g=d!=-1?jsworld._formatFractionPart(e.fraction,d):e.fraction;var h=g.length?f+this.lc.decimal_point+g:f;if(jsworld._hasOption("~",b)||c===0){return h}else{if(jsworld._hasOption("+",b)||c<0){if(c>0)return"+"+h;else if(c<0)return"-"+h;else return h}else{return h}}}};jsworld.DateTimeFormatter=function(a){if(typeof a!="object"||a._className!="jsworld.Locale")throw"Constructor error: You must provide a valid jsworld.Locale instance.";this.lc=a;this.formatDate=function(a){var b=null;if(typeof a=="string"){try{b=jsworld.parseIsoDate(a)}catch(c){b=jsworld.parseIsoDateTime(a)}}else if(a!==null&&typeof a=="object"){b=a}else{throw"Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string"}return this._applyFormatting(b,this.lc.d_fmt)};this.formatTime=function(a){var b=null;if(typeof a=="string"){try{b=jsworld.parseIsoTime(a)}catch(c){b=jsworld.parseIsoDateTime(a)}}else if(a!==null&&typeof a=="object"){b=a}else{throw"Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string"}return this._applyFormatting(b,this.lc.t_fmt)};this.formatDateTime=function(a){var b=null;if(typeof a=="string"){b=jsworld.parseIsoDateTime(a)}else if(a!==null&&typeof a=="object"){b=a}else{throw"Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string"}return this._applyFormatting(b,this.lc.d_t_fmt)};this._applyFormatting=function(a,b){b=b.replace(/%%/g,"%");b=b.replace(/%a/g,this.lc.abday[a.getDay()]);b=b.replace(/%A/g,this.lc.day[a.getDay()]);b=b.replace(/%b/g,this.lc.abmon[a.getMonth()]);b=b.replace(/%B/g,this.lc.mon[a.getMonth()]);b=b.replace(/%d/g,jsworld._zeroPad(a.getDate(),2));b=b.replace(/%e/g,jsworld._spacePad(a.getDate(),2));b=b.replace(/%F/g,a.getFullYear()+"-"+jsworld._zeroPad(a.getMonth()+1,2)+"-"+jsworld._zeroPad(a.getDate(),2));b=b.replace(/%h/g,this.lc.abmon[a.getMonth()]);b=b.replace(/%H/g,jsworld._zeroPad(a.getHours(),2));b=b.replace(/%I/g,jsworld._zeroPad(this._hours12(a.getHours()),2));b=b.replace(/%k/g,a.getHours());b=b.replace(/%l/g,this._hours12(a.getHours()));b=b.replace(/%m/g,jsworld._zeroPad(a.getMonth()+1,2));b=b.replace(/%n/g,"\n");b=b.replace(/%M/g,jsworld._zeroPad(a.getMinutes(),2));b=b.replace(/%p/g,this._getAmPm(a.getHours()));b=b.replace(/%P/g,this._getAmPm(a.getHours()).toLocaleLowerCase());b=b.replace(/%R/g,jsworld._zeroPad(a.getHours(),2)+":"+jsworld._zeroPad(a.getMinutes(),2));b=b.replace(/%S/g,jsworld._zeroPad(a.getSeconds(),2));b=b.replace(/%T/g,jsworld._zeroPad(a.getHours(),2)+":"+jsworld._zeroPad(a.getMinutes(),2)+":"+jsworld._zeroPad(a.getSeconds(),2));b=b.replace(/%w/g,this.lc.day[a.getDay()]);b=b.replace(/%y/g,(new String(a.getFullYear())).substring(2));b=b.replace(/%Y/g,a.getFullYear());b=b.replace(/%Z/g,"");b=b.replace(/%[a-zA-Z]/g,"");return b};this._hours12=function(a){if(a===0)return 12;else if(a>12)return a-12;else return a};this._getAmPm=function(a){if(a===0||a>12)return this.lc.pm;else return this.lc.am}};jsworld.MonetaryFormatter=function(a,b,c){if(typeof a!="object"||a._className!="jsworld.Locale")throw"Constructor error: You must provide a valid jsworld.Locale instance";this.lc=a;this.currencyFractionDigits={AFN:0,ALL:0,AMD:0,BHD:3,BIF:0,BYR:0,CLF:0,CLP:0,COP:0,CRC:0,DJF:0,GNF:0,GYD:0,HUF:0,IDR:0,IQD:0,IRR:0,ISK:0,JOD:3,JPY:0,KMF:0,KRW:0,KWD:3,LAK:0,LBP:0,LYD:3,MGA:0,MMK:0,MNT:0,MRO:0,MUR:0,OMR:3,PKR:0,PYG:0,RSD:0,RWF:0,SLL:0,SOS:0,STD:0,SYP:0,TND:3,TWD:0,TZS:0,UGX:0,UZS:0,VND:0,VUV:0,XAF:0,XOF:0,XPF:0,YER:0,ZMK:0};if(typeof b=="string"){this.currencyCode=b.toUpperCase();var d=this.currencyFractionDigits[this.currencyCode];if(typeof d!="number")d=2;this.lc.frac_digits=d;this.lc.int_frac_digits=d}else{this.currencyCode=this.lc.int_curr_symbol.substring(0,3).toUpperCase()}this.intSep=this.lc.int_curr_symbol.charAt(3);if(this.currencyCode==this.lc.int_curr_symbol.substring(0,3)){this.internationalFormatting=false;this.curSym=this.lc.currency_symbol}else{if(typeof c=="string"){this.curSym=c;this.internationalFormatting=false}else{this.internationalFormatting=true}}this.getCurrencySymbol=function(){return this.curSym};this.currencySymbolPrecedes=function(a){if(typeof a=="string"&&a=="i"){if(this.lc.int_p_cs_precedes==1)return true;else return false}else{if(this.internationalFormatting){if(this.lc.int_p_cs_precedes==1)return true;else return false}else{if(this.lc.p_cs_precedes==1)return true;else return false}}};this.getDecimalPoint=function(){return this.lc.mon_decimal_point};this.getFractionalDigits=function(a){if(typeof a=="string"&&a=="i"){return this.lc.int_frac_digits}else{if(this.internationalFormatting)return this.lc.int_frac_digits;else return this.lc.frac_digits}};this.format=function(a,b){var c;if(typeof a=="string"){a=jsworld._trim(a);c=parseFloat(a);if(typeof c!="number"||isNaN(c))throw"Error: Amount string not a number"}else if(typeof a=="number"){c=a}else{throw"Error: Amount not a number"}var d=jsworld._getPrecision(b);if(d==-1){if(this.internationalFormatting||jsworld._hasOption("i",b))d=this.lc.int_frac_digits;else d=this.lc.frac_digits}c=Math.round(c*Math.pow(10,d))/Math.pow(10,d);var e=jsworld._splitNumber(String(c));var f;if(c===0)f="0";else f=jsworld._hasOption("^",b)?e.integer:jsworld._formatIntegerPart(e.integer,this.lc.mon_grouping,this.lc.mon_thousands_sep);var g;if(d==-1){if(this.internationalFormatting||jsworld._hasOption("i",b))g=jsworld._formatFractionPart(e.fraction,this.lc.int_frac_digits);else g=jsworld._formatFractionPart(e.fraction,this.lc.frac_digits)}else{g=jsworld._formatFractionPart(e.fraction,d)}var h;if(this.lc.frac_digits>0||g.length)h=f+this.lc.mon_decimal_point+g;else h=f;if(jsworld._hasOption("~",b)){return h}else{var i=jsworld._hasOption("!",b)?true:false;var j=c<0?"-":"+";if(this.internationalFormatting||jsworld._hasOption("i",b)){if(i)return this._formatAsInternationalCurrencyWithNoSym(j,h);else return this._formatAsInternationalCurrency(j,h)}else{if(i)return this._formatAsLocalCurrencyWithNoSym(j,h);else return this._formatAsLocalCurrency(j,h)}}};this._formatAsLocalCurrency=function(a,b){if(a=="+"){if(this.lc.p_sign_posn===0&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return"("+b+this.curSym+")"}else if(this.lc.p_sign_posn===0&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return"("+this.curSym+b+")"}else if(this.lc.p_sign_posn===0&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return"("+b+" "+this.curSym+")"}else if(this.lc.p_sign_posn===0&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return"("+this.curSym+" "+b+")"}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return this.lc.positive_sign+b+this.curSym}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+this.curSym+b}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return this.lc.positive_sign+b+" "+this.curSym}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+this.curSym+" "+b}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===0){return this.lc.positive_sign+" "+b+this.curSym}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+" "+this.curSym+b}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return b+this.curSym+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return this.curSym+b+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return b+" "+this.curSym+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return this.curSym+" "+b+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===0){return b+this.curSym+" "+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===1){return this.curSym+b+" "+this.lc.positive_sign}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return b+this.lc.positive_sign+this.curSym}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+this.curSym+b}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return b+" "+this.lc.positive_sign+this.curSym}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+this.curSym+" "+b}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===0){return b+this.lc.positive_sign+" "+this.curSym}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+" "+this.curSym+b}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return b+this.curSym+this.lc.positive_sign}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return this.curSym+this.lc.positive_sign+b}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return b+" "+this.curSym+this.lc.positive_sign}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return this.curSym+this.lc.positive_sign+" "+b}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===0){return b+this.curSym+" "+this.lc.positive_sign}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===1){return this.curSym+" "+this.lc.positive_sign+b}}else if(a=="-"){if(this.lc.n_sign_posn===0&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return"("+b+this.curSym+")"}else if(this.lc.n_sign_posn===0&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return"("+this.curSym+b+")"}else if(this.lc.n_sign_posn===0&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return"("+b+" "+this.curSym+")"}else if(this.lc.n_sign_posn===0&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return"("+this.curSym+" "+b+")"}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return this.lc.negative_sign+b+this.curSym}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+this.curSym+b}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return this.lc.negative_sign+b+" "+this.curSym}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+this.curSym+" "+b}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===0){return this.lc.negative_sign+" "+b+this.curSym}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+" "+this.curSym+b}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return b+this.curSym+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return this.curSym+b+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return b+" "+this.curSym+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return this.curSym+" "+b+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===0){return b+this.curSym+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===1){return this.curSym+b+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return b+this.lc.negative_sign+this.curSym}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+this.curSym+b}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return b+" "+this.lc.negative_sign+this.curSym}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+this.curSym+" "+b}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===0){return b+this.lc.negative_sign+" "+this.curSym}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+" "+this.curSym+b}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return b+this.curSym+this.lc.negative_sign}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return this.curSym+this.lc.negative_sign+b}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return b+" "+this.curSym+this.lc.negative_sign}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return this.curSym+this.lc.negative_sign+" "+b}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===0){return b+this.curSym+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===1){return this.curSym+" "+this.lc.negative_sign+b}}throw"Error: Invalid POSIX LC MONETARY definition"};this._formatAsInternationalCurrency=function(a,b){if(a=="+"){if(this.lc.int_p_sign_posn===0&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return"("+b+this.currencyCode+")"}else if(this.lc.int_p_sign_posn===0&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return"("+this.currencyCode+b+")"}else if(this.lc.int_p_sign_posn===0&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return"("+b+this.intSep+this.currencyCode+")"}else if(this.lc.int_p_sign_posn===0&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return"("+this.currencyCode+this.intSep+b+")"}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return this.lc.positive_sign+b+this.currencyCode}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.currencyCode+b}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return this.lc.positive_sign+b+this.intSep+this.currencyCode}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.currencyCode+this.intSep+b}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===0){return this.lc.positive_sign+this.intSep+b+this.currencyCode}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.intSep+this.currencyCode+b}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return b+this.currencyCode+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return this.currencyCode+b+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return b+this.intSep+this.currencyCode+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return this.currencyCode+this.intSep+b+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===0){return b+this.currencyCode+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===1){return this.currencyCode+b+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return b+this.lc.positive_sign+this.currencyCode}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.currencyCode+b}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return b+this.intSep+this.lc.positive_sign+this.currencyCode}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.currencyCode+this.intSep+b}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===0){return b+this.lc.positive_sign+this.intSep+this.currencyCode}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.intSep+this.currencyCode+b}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return b+this.currencyCode+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return this.currencyCode+this.lc.positive_sign+b}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return b+this.intSep+this.currencyCode+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return this.currencyCode+this.lc.positive_sign+this.intSep+b}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===0){return b+this.currencyCode+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===1){return this.currencyCode+this.intSep+this.lc.positive_sign+b}}else if(a=="-"){if(this.lc.int_n_sign_posn===0&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return"("+b+this.currencyCode+")"}else if(this.lc.int_n_sign_posn===0&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return"("+this.currencyCode+b+")"}else if(this.lc.int_n_sign_posn===0&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return"("+b+this.intSep+this.currencyCode+")"}else if(this.lc.int_n_sign_posn===0&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return"("+this.currencyCode+this.intSep+b+")"}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return this.lc.negative_sign+b+this.currencyCode}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.currencyCode+b}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return this.lc.negative_sign+b+this.intSep+this.currencyCode}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.currencyCode+this.intSep+b}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===0){return this.lc.negative_sign+this.intSep+b+this.currencyCode}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.intSep+this.currencyCode+b}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return b+this.currencyCode+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return this.currencyCode+b+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return b+this.intSep+this.currencyCode+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return this.currencyCode+this.intSep+b+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===0){return b+this.currencyCode+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===1){return this.currencyCode+b+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return b+this.lc.negative_sign+this.currencyCode}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.currencyCode+b}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return b+this.intSep+this.lc.negative_sign+this.currencyCode}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.currencyCode+this.intSep+b}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===0){return b+this.lc.negative_sign+this.intSep+this.currencyCode}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.intSep+this.currencyCode+b}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return b+this.currencyCode+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return this.currencyCode+this.lc.negative_sign+b}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return b+this.intSep+this.currencyCode+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return this.currencyCode+this.lc.negative_sign+this.intSep+b}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===0){return b+this.currencyCode+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===1){return this.currencyCode+this.intSep+this.lc.negative_sign+b}}throw"Error: Invalid POSIX LC MONETARY definition"};this._formatAsLocalCurrencyWithNoSym=function(a,b){if(a=="+"){if(this.lc.p_sign_posn===0){return"("+b+")"}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return this.lc.positive_sign+b}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+b}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return this.lc.positive_sign+b}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+b}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===0){return this.lc.positive_sign+" "+b}else if(this.lc.p_sign_posn===1&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+" "+b}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return b+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return b+" "+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return b+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.p_sign_posn===2&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===1){return b+" "+this.lc.positive_sign}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+b}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return b+" "+this.lc.positive_sign}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+" "+b}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.p_sign_posn===3&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+" "+b}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===0&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+b}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===0){return b+" "+this.lc.positive_sign}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===1&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+" "+b}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===0){return b+" "+this.lc.positive_sign}else if(this.lc.p_sign_posn===4&&this.lc.p_sep_by_space===2&&this.lc.p_cs_precedes===1){return this.lc.positive_sign+b}}else if(a=="-"){if(this.lc.n_sign_posn===0){return"("+b+")"}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return this.lc.negative_sign+b}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+b}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return this.lc.negative_sign+b}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+" "+b}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===0){return this.lc.negative_sign+" "+b}else if(this.lc.n_sign_posn===1&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+" "+b}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return b+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return b+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return b+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return b+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===0){return b+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===2&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===1){return b+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return b+this.lc.negative_sign}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+b}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return b+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+" "+b}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===0){return b+this.lc.negative_sign}else if(this.lc.n_sign_posn===3&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+" "+b}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===0){return b+this.lc.negative_sign}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===0&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+b}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===0){return b+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===1&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+" "+b}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===0){return b+" "+this.lc.negative_sign}else if(this.lc.n_sign_posn===4&&this.lc.n_sep_by_space===2&&this.lc.n_cs_precedes===1){return this.lc.negative_sign+b}}throw"Error: Invalid POSIX LC MONETARY definition"};this._formatAsInternationalCurrencyWithNoSym=function(a,b){if(a=="+"){if(this.lc.int_p_sign_posn===0){return"("+b+")"}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return this.lc.positive_sign+b}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+b}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return this.lc.positive_sign+b}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.intSep+b}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===0){return this.lc.positive_sign+this.intSep+b}else if(this.lc.int_p_sign_posn===1&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.intSep+b}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return b+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return b+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return b+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===0){return b+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===2&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===1){return b+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+b}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return b+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.intSep+b}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===3&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.intSep+b}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===0){return b+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===0&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+b}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===0){return b+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===1&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+this.intSep+b}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===0){return b+this.intSep+this.lc.positive_sign}else if(this.lc.int_p_sign_posn===4&&this.lc.int_p_sep_by_space===2&&this.lc.int_p_cs_precedes===1){return this.lc.positive_sign+b}}else if(a=="-"){if(this.lc.int_n_sign_posn===0){return"("+b+")"}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return this.lc.negative_sign+b}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+b}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return this.lc.negative_sign+b}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.intSep+b}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===0){return this.lc.negative_sign+this.intSep+b}else if(this.lc.int_n_sign_posn===1&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.intSep+b}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return b+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return b+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return b+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return b+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===0){return b+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===2&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===1){return b+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return b+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+b}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return b+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.intSep+b}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===0){return b+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===3&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.intSep+b}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===0){return b+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===0&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+b}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===0){return b+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===1&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+this.intSep+b}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===0){return b+this.intSep+this.lc.negative_sign}else if(this.lc.int_n_sign_posn===4&&this.lc.int_n_sep_by_space===2&&this.lc.int_n_cs_precedes===1){return this.lc.negative_sign+b}}throw"Error: Invalid POSIX LC_MONETARY definition"}};jsworld.NumericParser=function(a){if(typeof a!="object"||a._className!="jsworld.Locale")throw"Constructor error: You must provide a valid jsworld.Locale instance";this.lc=a;this.parse=function(a){if(typeof a!="string")throw"Parse error: Argument must be a string";var b=jsworld._trim(a);b=jsworld._stringReplaceAll(a,this.lc.thousands_sep,"");b=jsworld._stringReplaceAll(b,this.lc.decimal_point,".");if(jsworld._isNumber(b))return parseFloat(b,10);else throw"Parse error: Invalid number string"}};jsworld.DateTimeParser=function(a){if(typeof a!="object"||a._className!="jsworld.Locale")throw"Constructor error: You must provide a valid jsworld.Locale instance.";this.lc=a;this.parseTime=function(a){if(typeof a!="string")throw"Parse error: Argument must be a string";var b=this._extractTokens(this.lc.t_fmt,a);var c=false;if(b.hour!==null&&b.minute!==null&&b.second!==null){c=true}else if(b.hourAmPm!==null&&b.am!==null&&b.minute!==null&&b.second!==null){if(b.am){b.hour=parseInt(b.hourAmPm,10)}else{if(b.hourAmPm==12)b.hour=0;else b.hour=parseInt(b.hourAmPm,10)+12}c=true}if(c)return jsworld._zeroPad(b.hour,2)+":"+jsworld._zeroPad(b.minute,2)+":"+jsworld._zeroPad(b.second,2);else throw"Parse error: Invalid/ambiguous time string"};this.parseDate=function(a){if(typeof a!="string")throw"Parse error: Argument must be a string";var b=this._extractTokens(this.lc.d_fmt,a);var c=false;if(b.year!==null&&b.month!==null&&b.day!==null){c=true}if(c)return jsworld._zeroPad(b.year,4)+"-"+jsworld._zeroPad(b.month,2)+"-"+jsworld._zeroPad(b.day,2);else throw"Parse error: Invalid date string"};this.parseDateTime=function(a){if(typeof a!="string")throw"Parse error: Argument must be a string";var b=this._extractTokens(this.lc.d_t_fmt,a);var c=false;var d=false;if(b.hour!==null&&b.minute!==null&&b.second!==null){c=true}else if(b.hourAmPm!==null&&b.am!==null&&b.minute!==null&&b.second!==null){if(b.am){b.hour=parseInt(b.hourAmPm,10)}else{if(b.hourAmPm==12)b.hour=0;else b.hour=parseInt(b.hourAmPm,10)+12}c=true}if(b.year!==null&&b.month!==null&&b.day!==null){d=true}if(d&&c)return jsworld._zeroPad(b.year,4)+"-"+jsworld._zeroPad(b.month,2)+"-"+jsworld._zeroPad(b.day,2)+" "+jsworld._zeroPad(b.hour,2)+":"+jsworld._zeroPad(b.minute,2)+":"+jsworld._zeroPad(b.second,2);else throw"Parse error: Invalid/ambiguous date/time string"};this._extractTokens=function(a,b){var c={year:null,month:null,day:null,hour:null,hourAmPm:null,am:null,minute:null,second:null,weekday:null};while(a.length>0){if(a.charAt(0)=="%"&&a.charAt(1)!=""){var d=a.substring(0,2);if(d=="%%"){b=b.substring(1)}else if(d=="%a"){for(var e=0;e31)throw"Parse error: Unrecognised day of the month (%e)";b=b.substring(f.length)}else if(d=="%F"){if(/^\d\d\d\d/.test(b)){c.year=parseInt(b.substring(0,4),10);b=b.substring(4)}else{throw"Parse error: Unrecognised date (%F)"}if(jsworld._stringStartsWith(b,"-"))b=b.substring(1);else throw"Parse error: Unrecognised date (%F)";if(/^0[1-9]|1[0-2]/.test(b)){c.month=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised date (%F)";if(jsworld._stringStartsWith(b,"-"))b=b.substring(1);else throw"Parse error: Unrecognised date (%F)";if(/^0[1-9]|[1-2][0-9]|3[0-1]/.test(b)){c.day=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised date (%F)"}else if(d=="%H"){if(/^[0-1][0-9]|2[0-3]/.test(b)){c.hour=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised hour (%H)"}else if(d=="%I"){if(/^0[1-9]|1[0-2]/.test(b)){c.hourAmPm=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised hour (%I)"}else if(d=="%k"){var g=b.match(/^(\d{1,2})/);c.hour=parseInt(g,10);if(isNaN(c.hour)||c.hour<0||c.hour>23)throw"Parse error: Unrecognised hour (%k)";b=b.substring(g.length)}else if(d=="%l"){var g=b.match(/^(\d{1,2})/);c.hourAmPm=parseInt(g,10);if(isNaN(c.hourAmPm)||c.hourAmPm<1||c.hourAmPm>12)throw"Parse error: Unrecognised hour (%l)";b=b.substring(g.length)}else if(d=="%m"){if(/^0[1-9]|1[0-2]/.test(b)){c.month=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised month (%m)"}else if(d=="%M"){if(/^[0-5][0-9]/.test(b)){c.minute=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised minute (%M)"}else if(d=="%n"){if(b.charAt(0)=="\n")b=b.substring(1);else throw"Parse error: Unrecognised new line (%n)"}else if(d=="%p"){if(jsworld._stringStartsWith(b,this.lc.am)){c.am=true;b=b.substring(this.lc.am.length)}else if(jsworld._stringStartsWith(b,this.lc.pm)){c.am=false;b=b.substring(this.lc.pm.length)}else throw"Parse error: Unrecognised AM/PM value (%p)"}else if(d=="%P"){if(jsworld._stringStartsWith(b,this.lc.am.toLowerCase())){c.am=true;b=b.substring(this.lc.am.length)}else if(jsworld._stringStartsWith(b,this.lc.pm.toLowerCase())){c.am=false;b=b.substring(this.lc.pm.length)}else throw"Parse error: Unrecognised AM/PM value (%P)"}else if(d=="%R"){if(/^[0-1][0-9]|2[0-3]/.test(b)){c.hour=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised time (%R)";if(jsworld._stringStartsWith(b,":"))b=b.substring(1);else throw"Parse error: Unrecognised time (%R)";if(/^[0-5][0-9]/.test(b)){c.minute=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised time (%R)"}else if(d=="%S"){if(/^[0-5][0-9]/.test(b)){c.second=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised second (%S)"}else if(d=="%T"){if(/^[0-1][0-9]|2[0-3]/.test(b)){c.hour=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised time (%T)";if(jsworld._stringStartsWith(b,":"))b=b.substring(1);else throw"Parse error: Unrecognised time (%T)";if(/^[0-5][0-9]/.test(b)){c.minute=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised time (%T)";if(jsworld._stringStartsWith(b,":"))b=b.substring(1);else throw"Parse error: Unrecognised time (%T)";if(/^[0-5][0-9]/.test(b)){c.second=parseInt(b.substring(0,2),10);b=b.substring(2)}else throw"Parse error: Unrecognised time (%T)"}else if(d=="%w"){if(/^\d/.test(b)){c.weekday=parseInt(b.substring(0,1),10);b=b.substring(1)}else throw"Parse error: Unrecognised weekday number (%w)"}else if(d=="%y"){if(/^\d\d/.test(b)){var h=parseInt(b.substring(0,2),10);if(h>50)c.year=1900+h;else c.year=2e3+h;b=b.substring(2)}else throw"Parse error: Unrecognised year (%y)"}else if(d=="%Y"){if(/^\d\d\d\d/.test(b)){c.year=parseInt(b.substring(0,4),10);b=b.substring(4)}else throw"Parse error: Unrecognised year (%Y)"}else if(d=="%Z"){if(a.length===0)break}a=a.substring(2)}else{if(a.charAt(0)!=b.charAt(0))throw'Parse error: Unexpected symbol "'+b.charAt(0)+'" in date/time string';a=a.substring(1);b=b.substring(1)}}return c}};jsworld.MonetaryParser=function(a){if(typeof a!="object"||a._className!="jsworld.Locale")throw"Constructor error: You must provide a valid jsworld.Locale instance";this.lc=a;this.parse=function(a){if(typeof a!="string")throw"Parse error: Argument must be a string";var b=this._detectCurrencySymbolType(a);var c,d;if(b=="local"){c="local";d=a.replace(this.lc.getCurrencySymbol(),"")}else if(b=="int"){c="int";d=a.replace(this.lc.getIntCurrencySymbol(),"")}else if(b=="none"){c="local";d=a}else throw"Parse error: Internal assert failure";d=jsworld._stringReplaceAll(d,this.lc.mon_thousands_sep,"");d=d.replace(this.lc.mon_decimal_point,".");d=d.replace(/\s*/g,"");d=this._removeLocalNonNegativeSign(d,c);d=this._normaliseNegativeSign(d,c);if(jsworld._isNumber(d))return parseFloat(d,10);else throw"Parse error: Invalid currency amount string"};this._detectCurrencySymbolType=function(a){if(this.lc.getCurrencySymbol().length>this.lc.getIntCurrencySymbol().length){if(a.indexOf(this.lc.getCurrencySymbol())!=-1)return"local";else if(a.indexOf(this.lc.getIntCurrencySymbol())!=-1)return"int";else return"none"}else{if(a.indexOf(this.lc.getIntCurrencySymbol())!=-1)return"int";else if(a.indexOf(this.lc.getCurrencySymbol())!=-1)return"local";else return"none"}};this._removeLocalNonNegativeSign=function(a,b){a=a.replace(this.lc.positive_sign,"");if((b=="local"&&this.lc.p_sign_posn===0||b=="int"&&this.lc.int_p_sign_posn===0)&&/\(\d+\.?\d*\)/.test(a)){a=a.replace("(","");a=a.replace(")","")}return a};this._normaliseNegativeSign=function(a,b){a=a.replace(this.lc.negative_sign,"-");if(b=="local"&&this.lc.n_sign_posn===0||b=="int"&&this.lc.int_n_sign_posn===0){if(/^\(\d+\.?\d*\)$/.test(a)){a=a.replace("(","");a=a.replace(")","");return"-"+a}}if(b=="local"&&this.lc.n_sign_posn==2||b=="int"&&this.lc.int_n_sign_posn==2){if(/^\d+\.?\d*-$/.test(a)){a=a.replace("-","");return"-"+a}}if(b=="local"&&this.lc.n_cs_precedes===0&&this.lc.n_sign_posn==3||b=="local"&&this.lc.n_cs_precedes===0&&this.lc.n_sign_posn==4||b=="int"&&this.lc.int_n_cs_precedes===0&&this.lc.int_n_sign_posn==3||b=="int"&&this.lc.int_n_cs_precedes===0&&this.lc.int_n_sign_posn==4){if(/^\d+\.?\d*-$/.test(a)){a=a.replace("-","");return"-"+a}}return a}} + + +if(typeof POSIX_LC == "undefined") var POSIX_LC = {}; + +POSIX_LC.en_US = { + "decimal_point" : ".", + "thousands_sep" : ",", + "grouping" : "3", + "abday" : ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"], + "day" : ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], + "abmon" : ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"], + "mon" : ["January","February","March","April","May","June","July","August","September","October","November","December"], + "d_fmt" : "%m/%e/%y", + "t_fmt" : "%I:%M:%S %p", + "d_t_fmt" : "%B %e, %Y %I:%M:%S %p %Z", + "am_pm" : ["AM","PM"], + "int_curr_symbol" : "USD ", + "currency_symbol" : "\u0024", + "mon_decimal_point" : ".", + "mon_thousands_sep" : ",", + "mon_grouping" : "3", + "positive_sign" : "", + "negative_sign" : "-", + "int_frac_digits" : 2, + "frac_digits" : 2, + "p_cs_precedes" : 1, + "n_cs_precedes" : 1, + "p_sep_by_space" : 0, + "n_sep_by_space" : 0, + "p_sign_posn" : 1, + "n_sign_posn" : 1, + "int_p_cs_precedes" : 1, + "int_n_cs_precedes" : 1, + "int_p_sep_by_space" : 0, + "int_n_sep_by_space" : 0, + "int_p_sign_posn" : 1, + "int_n_sign_posn" : 1 +} + +if(typeof POSIX_LC == "undefined") var POSIX_LC = {}; + +POSIX_LC.fr_FR = { + "decimal_point" : ",", + "thousands_sep" : "\u00a0", + "grouping" : "3", + "abday" : ["dim.","lun.","mar.", + "mer.","jeu.","ven.", + "sam."], + "day" : ["dimanche","lundi","mardi", + "mercredi","jeudi","vendredi", + "samedi"], + "abmon" : ["janv.","f\u00e9vr.","mars", + "avr.","mai","juin", + "juil.","ao\u00fbt","sept.", + "oct.","nov.","d\u00e9c."], + "mon" : ["janvier","f\u00e9vrier","mars", + "avril","mai","juin", + "juillet","ao\u00fbt","septembre", + "octobre","novembre","d\u00e9cembre"], + "d_fmt" : "%d/%m/%y", + "t_fmt" : "%H:%M:%S", + "d_t_fmt" : "%e %B %Y %H:%M:%S %Z", + "am_pm" : ["AM","PM"], + "int_curr_symbol" : "EUR ", + "currency_symbol" : "\u20ac", + "mon_decimal_point" : ",", + "mon_thousands_sep" : "\u00a0", + "mon_grouping" : "3", + "positive_sign" : "", + "negative_sign" : "-", + "int_frac_digits" : 2, + "frac_digits" : 2, + "p_cs_precedes" : 0, + "n_cs_precedes" : 0, + "p_sep_by_space" : 1, + "n_sep_by_space" : 1, + "p_sign_posn" : 1, + "n_sign_posn" : 1, + "int_p_cs_precedes" : 0, + "int_n_cs_precedes" : 0, + "int_p_sep_by_space" : 1, + "int_n_sep_by_space" : 1, + "int_p_sign_posn" : 1, + "int_n_sign_posn" : 1 +}; + +/** https://github.com/csnover/js-iso8601 */(function(n,f){var u=n.parse,c=[1,4,5,6,7,10,11];n.parse=function(t){var i,o,a=0;if(o=/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(t)){for(var v=0,r;r=c[v];++v)o[r]=+o[r]||0;o[2]=(+o[2]||1)-1,o[3]=+o[3]||1,o[8]!=="Z"&&o[9]!==f&&(a=o[10]*60+o[11],o[9]==="+"&&(a=0-a)),i=n.UTC(o[1],o[2],o[3],o[4],o[5]+a,o[6],o[7])}else i=u?u(t):NaN;return i}})(Date) + +/*! + * geo-location-javascript v0.4.3 + * http://code.google.com/p/geo-location-javascript/ + * + * Copyright (c) 2009 Stan Wiechers + * Licensed under the MIT licenses. + * + * Revision: $Rev: 68 $: + * Author: $Author: whoisstan $: + * Date: $Date: 2010-02-15 13:42:19 +0100 (Mon, 15 Feb 2010) $: + */ +var geo_position_js=function() { + + var pub = {}; + var provider=null; + + pub.getCurrentPosition = function(successCallback,errorCallback,options) + { + provider.getCurrentPosition(successCallback, errorCallback,options); + } + + pub.init = function() + { + try + { + if (typeof(geo_position_js_simulator)!="undefined") + { + provider=geo_position_js_simulator; + } + else if (typeof(bondi)!="undefined" && typeof(bondi.geolocation)!="undefined") + { + provider=bondi.geolocation; + } + else if (typeof(navigator.geolocation)!="undefined") + { + provider=navigator.geolocation; + pub.getCurrentPosition = function(successCallback, errorCallback, options) + { + function _successCallback(p) + { + //for mozilla geode,it returns the coordinates slightly differently + if(typeof(p.latitude)!="undefined") + { + successCallback({timestamp:p.timestamp, coords: {latitude:p.latitude,longitude:p.longitude}}); + } + else + { + successCallback(p); + } + } + provider.getCurrentPosition(_successCallback,errorCallback,options); + } + } + else if(typeof(window.google)!="undefined" && typeof(google.gears)!="undefined") + { + provider=google.gears.factory.create('beta.geolocation'); + } + else if ( typeof(Mojo) !="undefined" && typeof(Mojo.Service.Request)!="Mojo.Service.Request") + { + provider=true; + pub.getCurrentPosition = function(successCallback, errorCallback, options) + { + + parameters={}; + if(options) + { + //http://developer.palm.com/index.php?option=com_content&view=article&id=1673#GPS-getCurrentPosition + if (options.enableHighAccuracy && options.enableHighAccuracy==true) + { + parameters.accuracy=1; + } + if (options.maximumAge) + { + parameters.maximumAge=options.maximumAge; + } + if (options.responseTime) + { + if(options.responseTime<5) + { + parameters.responseTime=1; + } + else if (options.responseTime<20) + { + parameters.responseTime=2; + } + else + { + parameters.timeout=3; + } + } + } + + + r=new Mojo.Service.Request('palm://com.palm.location', { + method:"getCurrentPosition", + parameters:parameters, + onSuccess: function(p){successCallback({timestamp:p.timestamp, coords: {latitude:p.latitude, longitude:p.longitude,heading:p.heading}});}, + onFailure: function(e){ + if (e.errorCode==1) + { + errorCallback({code:3,message:"Timeout"}); + } + else if (e.errorCode==2) + { + errorCallback({code:2,message:"Position Unavailable"}); + } + else + { + errorCallback({code:0,message:"Unknown Error: webOS-code"+errorCode}); + } + } + }); + } + + } + else if (typeof(device)!="undefined" && typeof(device.getServiceObject)!="undefined") + { + provider=device.getServiceObject("Service.Location", "ILocation"); + + //override default method implementation + pub.getCurrentPosition = function(successCallback, errorCallback, options) + { + function callback(transId, eventCode, result) { + if (eventCode == 4) + { + errorCallback({message:"Position unavailable", code:2}); + } + else + { + //no timestamp of location given? + successCallback({timestamp:null, coords: {latitude:result.ReturnValue.Latitude, longitude:result.ReturnValue.Longitude, altitude:result.ReturnValue.Altitude,heading:result.ReturnValue.Heading}}); + } + } + //location criteria + var criteria = new Object(); + criteria.LocationInformationClass = "BasicLocationInformation"; + //make the call + provider.ILocation.GetLocation(criteria,callback); + } + } + } + catch (e){ + alert("error="+e); + if(typeof(console)!="undefined") + { + console.log(e); + } + return false; + } + return provider!=null; + } + + + return pub; +}(); +// Couldn't get unminified version to work , go here for docs => https://github.com/iamnoah/writeCapture +(function(E,a){var j=a.document;function A(Q){var Z=j.createElement("div");j.body.insertBefore(Z,null);E.replaceWith(Z,'\n \n
\n
\n \n\n
\n
\n \n
\n

'); + __out.push(__sanitize(t('Invite Link'))); + __out.push(' '); + __out.push(__sanitize(USER.referral_url)); + __out.push('

\n\n \n\n
\n\n'); + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}}, "templates/clients/login": function(exports, require, module) {module.exports = function(__obj) { + if (!__obj) __obj = {}; + var __out = [], __capture = function(callback) { + var out = __out, result; + __out = []; + callback.call(this); + result = __out.join(''); + __out = out; + return __safe(result); + }, __sanitize = function(value) { + if (value && value.ecoSafe) { + return value; + } else if (typeof value !== 'undefined' && value != null) { + return __escape(value); + } else { + return ''; + } + }, __safe, __objSafe = __obj.safe, __escape = __obj.escape; + __safe = __obj.safe = function(value) { + if (value && value.ecoSafe) { + return value; + } else { + if (!(typeof value !== 'undefined' && value != null)) value = ''; + var result = new String(value); + result.ecoSafe = true; + return result; + } + }; + if (!__escape) { + __escape = __obj.escape = function(value) { + return ('' + value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + } + (function() { + (function() { + __out.push('
\n\t

'); + __out.push(__sanitize(t('Sign In'))); + __out.push('

\n\t
\n\t\t
\n\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\n\t\t\t
\n\n
\n\n

'); + __out.push(__sanitize(t('Forgot Password?'))); + __out.push('

\n\n\t\t
\n\t
\n
\n\n
\n
\n'); + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}}, "templates/clients/modules/credit_card": function(exports, require, module) {module.exports = function(__obj) { + if (!__obj) __obj = {}; + var __out = [], __capture = function(callback) { + var out = __out, result; + __out = []; + callback.call(this); + result = __out.join(''); + __out = out; + return __safe(result); + }, __sanitize = function(value) { + if (value && value.ecoSafe) { + return value; + } else if (typeof value !== 'undefined' && value != null) { + return __escape(value); + } else { + return ''; + } + }, __safe, __objSafe = __obj.safe, __escape = __obj.escape; + __safe = __obj.safe = function(value) { + if (value && value.ecoSafe) { + return value; + } else { + if (!(typeof value !== 'undefined' && value != null)) value = ''; + var result = new String(value); + result.ecoSafe = true; + return result; + } + }; + if (!__escape) { + __escape = __obj.escape = function(value) { + return ('' + value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + } + (function() { + (function() { + var printCard; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + if (this.cards === "new") { + __out.push('\n
\n
\n
\n
\n
\n
\n \n \n
\n
\n
\n
\n \n \n
\n
\n \n \n
\n
\n
\n
\n \n \n
\n
\n
\n \n \n
\n
\n
\n \n \n
\n
\n
\n \n
\n
\n
\n'); + } else { + __out.push('\n '); + printCard = __bind(function(card, index) { + var exp, style; + __out.push('\n
\n '); + style = "background-position:-173px"; + __out.push('\n '); + if (card.get("card_type") === "Visa") { + style = "background-position:0px"; + } + __out.push('\n '); + if (card.get("card_type") === "MasterCard") { + style = "background-position:-42px"; + } + __out.push('\n '); + if (card.get("card_type") === "American Express") { + style = "background-position:-130px"; + } + __out.push('\n '); + if (card.get("card_type") === "Discover Card") { + style = "background-position:-85px"; + } + __out.push('\n
\n
\n ****'); + __out.push(__sanitize(card.get("card_number"))); + __out.push('\n \n '); + if (card.get("card_expiration")) { + __out.push('\n '); + __out.push(__sanitize(t('Expiry'))); + __out.push('\n '); + exp = card.get('card_expiration').split('-'); + __out.push('\n '); + __out.push(__sanitize("" + exp[0] + "-" + exp[1])); + __out.push('\n '); + } + __out.push('\n \n \n \n '); + if (card.get("default")) { + __out.push('\n ('); + __out.push(__sanitize(t('default card'))); + __out.push(')\n '); + } + __out.push('\n '); + if (this.cards.length > 1 && !card.get("default")) { + __out.push('\n '); + __out.push(__sanitize(t('make default'))); + __out.push('\n '); + } + __out.push('\n \n '); + __out.push(__sanitize(t('Edit'))); + __out.push('\n \n '); + if (this.cards.length > 1) { + __out.push('\n '); + __out.push(__sanitize(t('Delete'))); + __out.push('\n '); + } + __out.push('\n
\n '); + _.each(this.cards.models, printCard); + __out.push('\n
\n
\n\n'); + } + __out.push('\n'); + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}}, "templates/clients/modules/sub_header": function(exports, require, module) {module.exports = function(__obj) { + if (!__obj) __obj = {}; + var __out = [], __capture = function(callback) { + var out = __out, result; + __out = []; + callback.call(this); + result = __out.join(''); + __out = out; + return __safe(result); + }, __sanitize = function(value) { + if (value && value.ecoSafe) { + return value; + } else if (typeof value !== 'undefined' && value != null) { + return __escape(value); + } else { + return ''; + } + }, __safe, __objSafe = __obj.safe, __escape = __obj.escape; + __safe = __obj.safe = function(value) { + if (value && value.ecoSafe) { + return value; + } else { + if (!(typeof value !== 'undefined' && value != null)) value = ''; + var result = new String(value); + result.ecoSafe = true; + return result; + } + }; + if (!__escape) { + __escape = __obj.escape = function(value) { + return ('' + value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + } + (function() { + (function() { + __out.push('
\n
'); + __out.push(__sanitize(this.heading)); + __out.push('
\n
\n '); + if (window.USER.first_name) { + __out.push('\n '); + __out.push(__sanitize(t('Hello Greeting', { + name: USER.first_name + }))); + __out.push('\n '); + } + __out.push('\n
\n
\n
\n'); + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}}, "templates/clients/promotions": function(exports, require, module) {module.exports = function(__obj) { + if (!__obj) __obj = {}; + var __out = [], __capture = function(callback) { + var out = __out, result; + __out = []; + callback.call(this); + result = __out.join(''); + __out = out; + return __safe(result); + }, __sanitize = function(value) { + if (value && value.ecoSafe) { + return value; + } else if (typeof value !== 'undefined' && value != null) { + return __escape(value); + } else { + return ''; + } + }, __safe, __objSafe = __obj.safe, __escape = __obj.escape; + __safe = __obj.safe = function(value) { + if (value && value.ecoSafe) { + return value; + } else { + if (!(typeof value !== 'undefined' && value != null)) value = ''; + var result = new String(value); + result.ecoSafe = true; + return result; + } + }; + if (!__escape) { + __escape = __obj.escape = function(value) { + return ('' + value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + } + (function() { + (function() { + var promo, _i, _len, _ref; + __out.push(require('templates/clients/modules/sub_header').call(this, { + heading: t("Promotions") + })); + __out.push('\n\n
\n
\n
\n \n \n
\n
\n \n \n\n \n
\n '); + if (this.promos.length > 0) { + __out.push('\n
\n

'); + __out.push(__sanitize(t('Your Available Promotions'))); + __out.push('

\n \n \n\n \n \n \n \n \n \n \n \n '); + _ref = this.promos; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + promo = _ref[_i]; + __out.push('\n \n \n \n \n \n \n '); + } + __out.push('\n \n
'); + __out.push(__sanitize(t('Code'))); + __out.push(''); + __out.push(__sanitize(t('Details'))); + __out.push(''); + __out.push(__sanitize(t('Starts'))); + __out.push(''); + __out.push(__sanitize(t('Expires'))); + __out.push('
'); + __out.push(__sanitize(promo.code)); + __out.push(''); + __out.push(__sanitize(promo.description)); + __out.push(''); + __out.push(__sanitize(app.helpers.formatDate(promo.starts_at, true, "America/Los_Angeles"))); + __out.push(''); + __out.push(__sanitize(app.helpers.formatDate(promo.ends_at, true, "America/Los_Angeles"))); + __out.push('
\n
\n '); + } else { + __out.push('\n\n

'); + __out.push(__sanitize(t('No Active Promotions'))); + __out.push('

\n '); + } + __out.push('\n\n
\n
\n
\n'); + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}}, "templates/clients/request": function(exports, require, module) {module.exports = function(__obj) { + if (!__obj) __obj = {}; + var __out = [], __capture = function(callback) { + var out = __out, result; + __out = []; + callback.call(this); + result = __out.join(''); + __out = out; + return __safe(result); + }, __sanitize = function(value) { + if (value && value.ecoSafe) { + return value; + } else if (typeof value !== 'undefined' && value != null) { + return __escape(value); + } else { + return ''; + } + }, __safe, __objSafe = __obj.safe, __escape = __obj.escape; + __safe = __obj.safe = function(value) { + if (value && value.ecoSafe) { + return value; + } else { + if (!(typeof value !== 'undefined' && value != null)) value = ''; + var result = new String(value); + result.ecoSafe = true; + return result; + } + }; + if (!__escape) { + __escape = __obj.escape = function(value) { + return ('' + value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + } + (function() { + (function() { + var showFavoriteLocation; + showFavoriteLocation = function(location, index) { + var alphabet; + __out.push('\n '); + alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + __out.push('\n
\n '); + __out.push(__sanitize(location.nickname)); + return __out.push('\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n

'); + __out.push(__sanitize(t('Driver Name:'))); + __out.push('

\n

\n
\n

'); + __out.push(__sanitize(t('Driver #:'))); + __out.push('

\n

\n
\n

'); + __out.push(__sanitize(t('Pickup Address:'))); + __out.push('

\n

\n
\n ');
+      __out.push(__sanitize(t('Add to Favorite Locations')));
+      __out.push('\n
\n
\n

\n '); + __out.push(__sanitize(t('Nickname:'))); + __out.push('\n \n \n \n \n
\n
\n
\n
\n

'); + __out.push(__sanitize(t('Your last trip'))); + __out.push('

\n
\n \n ');
+      __out.push(__sanitize(t('Star')));
+      __out.push('\n ');
+      __out.push(__sanitize(t('Star')));
+      __out.push('\n ');
+      __out.push(__sanitize(t('Star')));
+      __out.push('\n ');
+      __out.push(__sanitize(t('Star')));
+      __out.push('\n ');
+      __out.push(__sanitize(t('Star')));
+      __out.push('\n \n \n
\n \n
\n \n
\n \n
\n \n\n
\n'); + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}}, "templates/shared/menu": function(exports, require, module) {module.exports = function(__obj) { + if (!__obj) __obj = {}; + var __out = [], __capture = function(callback) { + var out = __out, result; + __out = []; + callback.call(this); + result = __out.join(''); + __out = out; + return __safe(result); + }, __sanitize = function(value) { + if (value && value.ecoSafe) { + return value; + } else if (typeof value !== 'undefined' && value != null) { + return __escape(value); + } else { + return ''; + } + }, __safe, __objSafe = __obj.safe, __escape = __obj.escape; + __safe = __obj.safe = function(value) { + if (value && value.ecoSafe) { + return value; + } else { + if (!(typeof value !== 'undefined' && value != null)) value = ''; + var result = new String(value); + result.ecoSafe = true; + return result; + } + }; + if (!__escape) { + __escape = __obj.escape = function(value) { + return ('' + value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + } + (function() { + (function() { + __out.push('\n'); + }).call(this); + + }).call(__obj); + __obj.safe = __objSafe, __obj.escape = __escape; + return __out.join(''); +}}, "translations/en": function(exports, require, module) {(function() { + exports.translations = { + "Uber": "Uber", + "Sign Up": "Sign Up", + "Ride Request": "Ride Request", + "Invite Friends": "Invite Friends", + "Promotions": "Promotions", + "Billing": "Billing", + "Settings": "Settings", + "Forgot Password?": "Forgot Password?", + "Password Recovery": "Password Recovery", + "Login": "Login", + "Trip Detail": "Trip Detail", + "Password Reset": "Password Reset", + "Confirm Email": "Confirm Email", + "Request Ride": "Request Ride", + "Credit Card Number": "Credit Card Number", + "month": "month", + "01-Jan": "01-Jan", + "02-Feb": "02-Feb", + "03-Mar": "03-Mar", + "04-Apr": "04-Apr", + "05-May": "05-May", + "06-Jun": "06-Jun", + "07-Jul": "07-Jul", + "08-Aug": "08-Aug", + "09-Sep": "09-Sep", + "10-Oct": "10-Oct", + "11-Nov": "11-Nov", + "12-Dec": "12-Dec", + "year": "year", + "CVV": "CVV", + "Category": "Category", + "personal": "personal", + "business": "business", + "Default Credit Card": "Default Credit Card", + "Add Credit Card": "Add Credit Card", + "Expiry": "Expiry", + "default card": "default card", + "make default": "make default", + "Edit": "Edit", + "Delete": "Delete", + "Expiry Month": "Expiry Month", + "Expiry Year": "Expiry Year", + "Unable to Verify Card": "Unable to verify card at this time. Please try again later.", + "Credit Card Update Succeeded": "Your card has been successfully updated!", + "Credit Card Update Failed": "We couldn't save your changes. Please try again in a few minutes.", + "Credit Card Delete Succeeded": "Your card has been deleted!", + "Credit Card Delete Failed": "We were unable to delete your card. Please try again later.", + "Credit Card Update Category Succeeded": "Successfully changed card category!", + "Credit Card Update Category Failed": "We couldn't change your card category. Please try again in a few minutes.", + "Credit Card Update Default Succeeded": "Successfully changed default card!", + "Credit Card Update Default Failed": "We couldn't change your default card. Please try again in a few minutes.", + "Hello Greeting": "Hello, <%= name %>", + "Card Ending in": "Card Ending in", + "Trip Map": "Trip Map", + "Amount": "Amount: <%= amount %>", + "Last Attempt to Bill": "Last Attempt to Bill: <%= date %>", + "Charge": "Charge", + "Uber Credit Balance Note": "Your account has an UberCredit balance of <%= amount %>. When billing for trips, we'll deplete your UberCredit balance before applying charges to your credit card.", + "Please Add Credit Card": "Please add a credit card to bill your outstanding charges.", + "Credit Cards": "Credit Cards", + "add a new credit card": "add a new credit card", + "Account Balance": "Account Balance", + "Arrears": "Arrears", + "Billing Succeeded": "Your card was successfully billed.", + "Confirm Email Succeeded": "Successfully confirmed email token, redirecting to log in page...", + "Confirm Email Failed": "Unable to confirm email. Please contact support@uber.com if this problem persists.", + "Email Already Confirmed": "Your email address has already been confirmed, redirecting to log in page...", + "Credit Card Added": "Credit Card Added", + "No Credit Card": "No Credit Card", + "Mobile Number Confirmed": "Mobile Number Confirmed", + "No Confirmed Mobile": "No Confirmed Mobile", + "E-mail Address Confirmed": "E-mail Address Confirmed", + "No Confirmed E-mail": "No Confirmed E-mail", + 'Reply to sign up text': 'Reply "GO" to the text message you received at sign up.', + "Resend text message": "Resend text message", + "Click sign up link": "Click the link in the email you received at sign up.", + "Resend email": "Resend email", + "Add a credit card to ride": "Add a credit card and you'll be ready to ride Uber.", + "Your Most Recent Trip": "Your Most Recent Trip", + "details": "details", + "Your Trip History ": "Your Trip History ", + "Status": "Status", + "Here's how it works:": "Here's how it works:", + "Show all trips": "Show all trips", + "Set your location:": "Set your location:", + "App search for address": "iPhone/Android app: fix the pin or search for an address", + "SMS text address": "SMS: text your address to UBRCAB (827222)", + "Confirm pickup request": "Confirm your pickup request", + "Uber sends ETA": "Uber will send you an ETA (usually within 5-10 minutes)", + "Car arrives": "When your car is arriving, Uber will inform you again.", + "Ride to destination": "Hop in the car and tell the driver your destination.", + "Thank your driver": "That’s it! Please thank your driver but remember that your tip is included and no cash is necessary.", + "Trip started here": "Trip started here", + "Trip ended here": "Trip ended here", + "Sending Email": "Sending email...", + "Resend Email Succeeded": "We just sent the email. Please click on the confirmation link you recieve.", + "Resend Email Failed": "There was an error sending the email. Please contact support if the problem persists.", + "Resend Text Succeeded": 'We just sent the text message. Please reply "GO" to the message you recieve. It may take a few minutes for the message to reach you phone.', + "Resend Text Failed": "There was an error sending the text message. Please contact support if the problem persists.", + "Password Reset Error": "There was an error processing your password reset request.", + "New Password": "New Password", + "Forgot Password": "Forgot Password", + "Forgot Password Error": "Your email address could not be found. Please make sure to use the same email address you used when you signed up.", + "Forgot Password Success": "Please check your email for a link to reset your password.", + "Forgot Password Enter Email": 'Enter your email address and Uber will send you a link to reset your password. If you remember your password, you can sign in here.', + "Invite friends": "Invite friends", + "Give $ Get $": "Give $10, Get $10", + "Give $ Get $ Description": "Every friend you invite to Uber gets $10 of Uber credit. After someone you’ve invited takes his/her first ride, you get $10 of Uber credits too!", + "What are you waiting for?": "So, what are you waiting for? Invite away!", + "Tweet": "Tweet", + "Invite Link": "Email or IM this link to your friends:", + "Email Address": "Email Address", + "Reset Password": "Reset Password", + "Enter Promotion Code": "If you have a promotion code, enter it here:", + "Your Active Promotions": "Your Active Promotions", + "Code": "Code", + "Details": "Details", + "Trips Remaining": "Trips Remaining", + "Expires": "Expires", + "No Active Promotions": "There are no active promotions on your account.", + "Your Available Promotions": "Your Available Promotions", + "Where do you want us to pick you up?": "Where do you want us to pick you up?", + "Address to search": "Address to search", + "Search": "Search", + "Driver Name:": "Driver Name:", + "Driver #:": "Driver #:", + "Pickup Address:": "Pickup Address:", + "Add to Favorite Locations": "Add to Favorite Locations", + "Star": "Star", + "Nickname:": "Nickname:", + "Add": "Add", + "Your last trip": "Your last trip", + "Please rate your driver:": "Please rate your driver:", + "Comments: (optional)": "Comments: (optional)", + "Rate Trip": "Rate Trip", + "Pickup time:": "Pickup time:", + "Miles:": "Miles:", + "Trip time:": "Trip time:", + "Fare:": "Fare:", + "Favorite Locations": "Favorite Locations", + "Search Results": "Search Results", + "You have no favorite locations saved.": "You have no favorite locations saved.", + "Loading...": "Loading...", + "Request Pickup": "Request Pickup", + "Cancel Pickup": "Cancel Pickup", + "Requesting Closest Driver": "Requesting the closest driver to pick you up...", + "En Route": "You are currently en route...", + "Rate Last Trip": "Please rate your trip to make another request", + "Rate Before Submitting": "Please rate your trip before submitting the form", + "Address too short": "Address too short", + "or did you mean": "or did you mean", + "Search Address Failed": "Unable to find the given address. Please enter another address close to your location.", + "Sending pickup request...": "Sending pickup request...", + "Cancel Request Prompt": "Are you sure you want to cancel your request?", + "Cancel Request Arrived Prompt": 'Are you sure you want to cancel your request? Your driver has arrived so there is a $10 cancellation fee. It may help to call your driver now', + "Favorite Location Nickname Length Error": "Nickname has to be atleast 3 characters", + "Favorite Location Save Succeeded": "Location Saved!", + "Favorite Location Save Failed": "Unable to save your location. Please try again later.", + "Favorite Location Title": "Favorite Location <%= id %>", + "Search Location Title": "Search Location <%= id %>", + "ETA Message": "ETA: Around <%= minutes %> Minutes", + "Nearest Cab Message": "The closest driver is approximately <%= minutes %> minute(s) away", + "Arrival ETA Message": "Your Uber will arrive in about <%= minutes %> minute(s)", + "Arriving Now Message": "Your Uber is arriving now...", + "Rating Driver Failed": "Unable to contact server. Please try again later or email support if this issue persists.", + "Account Information": "Account Information", + "Mobile Phone Information": "Mobile Phone Information", + "settings": "settings", + "Information": "Information", + "Picture": "Picture", + "Change password": "Change password", + "Your current Picture": "Your current Picture", + "Your Favorite Locations": "Your Favorite Locations", + "You have no favorite locations saved.": "You have no favorite locations saved.", + "Purpose of Mobile": "We send text messages to your mobile phone to tell you when your driver is arriving. You can also request trips using text messages.", + "Country": "Country", + "Mobile Number": "Mobile Number", + "Submit": "Submit", + "Favorite Location": "Favorite Location", + "No Approximate Address": "Could not find an approximate address", + "Address:": "Address:", + "Information Update Succeeded": "Your information has been updated!", + "Information Update Failed": "We couldn't update your information. Please try again in few minutes or contact support if the problem persists.", + "Location Delete Succeeded": "Location deleted!", + "Location Delete Failed": "We were unable to delete your favorite location. Please try again later or contact support of the issue persists.", + "Location Edit Succeeded": "Changes Saved!", + "Location Edit Failed": "We couldn't save your changes. Please try again in a few minutes.", + "Picture Update Succeeded": "Your picture has been updated!", + "Picture Update Failed": "We couldn't change your picture. Please try again in a few minutes.", + "Personal Information": "Personal Information", + "Mobile Phone Number": "Mobile Phone Number", + "Payment Information": "Payment Information", + "Purpose of Credit Card": "We keep your credit card on file so that your trip go as fast as possible. You will not be charged until you take a trip.", + "Your card will not be charged until you take a trip.": "Your card will not be charged until you take a trip.", + "Credit Card Number": "Credit Card Number", + "Expiration Date": "Expiration Date", + "Promotion Code": "Promotion Code", + "Enter Promo Here": "If you have a code for a promotion, invitation or group deal, you can enter it here.", + "Promotion Code Input Label": "Promotion, Invite or Groupon Code (optional)", + "Terms and Conditions": "Terms and Conditions", + "HELP": "HELP", + "STOP": "STOP", + "Legal Information": "Legal Information", + "Sign Up Agreement": "By signing up, I agree to the Uber <%= terms_link %> and <%= privacy_link %> and understand that Uber is a request tool, not a transportation carrier.", + "Sign Up Agreement Error": "You must agree to the Uber Terms and Conditions and Privacy Policy to continue.", + "Message and Data Rates Disclosure": "Message and Data Rates May Apply. Reply <%= help_string %> to 827-222 for help. Reply <%= stop_string %> to 827-222 to stop texts. For additional assistance, visit support.uber.com or call (866) 576-1039. Supported Carriers: AT&T, Sprint, Verizon, and T-Mobile.", + "I Agree": "I agree to the Terms & Conditions and Privacy Policy", + "Security Code": "Security Code", + "Type of Card": "Type of Card", + "Personal": "Personal", + "Business": "Business", + "Code": "Code", + "Zip or Postal Code": "Zip or Postal Code", + "Your Trip": "Your Trip", + "Trip Info": "Trip Info", + "Request a fare review": "Request a fare review", + "Fare Review Submitted": "Your fare review has been submitted. We'll get back to you soon about your request. Sorry for any inconvenience this may have caused!", + "Fair Price Consideration": "We're committed to delivering Uber service at a fair price. Before requesting a fare review, please consider:", + "Your Fare Calculation": "Your Fare Calculation", + "Charges": "Charges", + "Discounts": "Discounts", + "Total Charge": "Total Charge", + "Uber pricing information": "Uber pricing information", + "Uber Pricing Information Message": "<%= learn_link %> is published on our website.", + "GPS Point Capture Disclosure": "Due to a finite number of GPS point captures, corners on your trip map may appear cut off or rounded. These minor inaccuracies result in a shorter measured distance, which always results in a cheaper trip.", + "Fare Review Note": "Please elaborate on why this trip requires a fare review. Your comments below will help us better establish the correct price for your trip:", + "Fare Review Error": "There was an error submitting the review. Please ensure that you have a message.", + "Sign In": "Sign In" + }; +}).call(this); +}, "translations/fr": function(exports, require, module) {(function() { + exports.translations = { + "Uber": "Uber", + "Sign Up": "Inscription", + "Ride Request": "Passer une Commande", + "Invite Friends": "Inviter vos Amis", + "Promotions": "Promotions", + "Billing": "Paiement", + "Settings": "Paramètres", + "Forgot Password?": "Mot de passe oublié ?", + "Password Recovery": "Récupération du mot de passe", + "Login": "Connexion", + "Trip Detail": "Détail de la Course", + "Password Reset": "Réinitialisation du mot de passe", + "Confirm Email": "Confirmation de l’e-mail", + "Request Ride": "Passer une Commande", + "Credit Card Number": "Numéro de Carte de Crédit", + "month": "mois", + "01-Jan": "01-Jan", + "02-Feb": "02-Fév", + "03-Mar": "03-Mar", + "04-Apr": "04-Avr", + "05-May": "05-Mai", + "06-Jun": "06-Juin", + "07-Jul": "07-Jui", + "08-Aug": "08-Aoû", + "09-Sep": "09-Sep", + "10-Oct": "10-Oct", + "11-Nov": "11-Nov", + "12-Dec": "12-Déc", + "year": "année", + "CVV": "Code de Sécurité", + "Category": "Type", + "personal": "personnel", + "business": "entreprise", + "Default Credit Card": "Carte par Défaut", + "Add Credit Card": "Ajouter une Carte", + "Expiry": "Expire", + "default card": "carte par défaut", + "make default": "choisir par défaut", + "Edit": "Modifier", + "Delete": "Supprimer", + "Expiry Month": "Mois d’Expiration", + "Expiry Year": "Année d’Expiration", + "Unable to Verify Card": "Impossible de vérifier la carte pour le moment. Merci de réessayer un peu plus tard.", + "Credit Card Update Succeeded": "Votre carte a été mise à jour avec succès !", + "Credit Card Update Failed": "Nous ne pouvons enregistrer vos changements. Merci de réessayer dans quelques minutes.", + "Credit Card Delete Succeeded": "Votre carte a été supprimée !", + "Credit Card Delete Failed": "Nous n’avons pas été en mesure de supprimer votre carte. Merci de réessayer plus tard.", + "Credit Card Update Category Succeeded": "Changement de catégorie de carte réussi !", + "Credit Card Update Category Failed": "Nous ne pouvons pas changer la catégorie de votre carte. Merci de réessayer dans quelques minutes.", + "Credit Card Update Default Succeeded": "Carte par défaut changée avec succès !", + "Credit Card Update Default Failed": "Nous ne pouvons pas changer votre carte par défaut. Merci de réessayer dans quelques minutes.", + "Hello Greeting": "Bonjour, <%= name %>", + "Card Ending in": "La carte expire dans", + "Trip Map": "Carte des Courses", + "Amount": "Montant: <%= amount %>", + "Last Attempt to Bill": "Dernière tentative de prélèvement : <%= date %>", + "Charge": "Débit", + "Uber Credit Balance Note": "Votre compte a un solde de <%= amount %> UberCredits. Lorsque nous facturons des courses, nous réduirons votre solde d’UberCredits avant de prélever votre carte de crédit.", + "Please Add Credit Card": "Merci d’ajouter une carte de crédit pour que nous puissions vous facturer.", + "Credit Cards": "Cartes de crédit", + "add a new credit card": "Ajouter une nouvelle carte de crédit", + "Account Balance": "Solde du compte", + "Arrears": "Arriérés", + "Billing Succeeded": "Votre carte a été correctement débitée.", + "Confirm Email Succeeded": "L’adresse e-mail a bien été validée, vous êtes redirigé vers le tableau de bord...", + "Confirm Email Failed": "Impossible de confirmer l’adresse e-mail. Merci de contacter support@uber.com si le problème persiste.", + "Credit Card Added": "Carte de crédit ajoutée", + "No Credit Card": "Pas de carte de crédit", + "Mobile Number Confirmed": "Numéro de téléphone confirmé", + "No Confirmed Mobile": "Pas de numéro de téléphone confirmé", + "E-mail Address Confirmed": "Adresse e-mail confirmée", + "No Confirmed E-mail": "Pas d’adresse e-mail confirmée", + 'Reply to sign up text': 'Répondre "GO" au SMS que vous avez reçu à l’inscription.', + "Resend text message": "Renvoyer le SMS", + "Click sign up link": "Cliquez sur le lien contenu dans l’e-mail reçu à l’inscription.", + "Resend email": "Renvoyer l’e-mail", + "Add a credit card to ride": "Ajouter une carte de crédit et vous serez prêt à voyager avec Uber.", + "Your Most Recent Trip": "Votre course la plus récente", + "details": "détails", + "Your Trip History": "Historique de votre trajet", + "Status": "Statut", + "Here's how it works:": "Voici comment ça marche :", + "Show all trips": "Montrer toutes les courses", + "Set your location:": "Définir votre position :", + "App search for address": "Application iPhone/Android : positionner la punaise ou rechercher une adresse", + "SMS text address": "SMS : envoyez votre adresse à UBRCAB (827222)", + "Confirm pickup request": "Validez la commande", + "Uber sends ETA": "Uber envoie un temps d’attente estimé (habituellement entre 5 et 10 minutes)", + "Car arrives": "Lorsque votre voiture arrive, Uber vous en informera encore..", + "Ride to destination": "Montez dans la voiture et donnez votre destination au chauffeur.", + "Thank your driver": "C’est tout ! Remerciez le chauffeur mais souvenez-vous que les pourboires sont compris et qu’il n’est pas nécessaire d’avoir du liquide sur soi.", + "Trip started here": "La course a commencé ici.", + "Trip ended here": "La course s’est terminée ici.", + "Sending Email": "Envoi de l’e-mail...", + "Resend Email Succeeded": "Nous venons d’envoyer l’e-mail. Merci de cliquer sur le lien de confirmation que vous avez reçu.", + "Resend Email Failed": "Il y a eu un problème lors de l’envoi de l’email. Merci de contacter le support si le problème persiste.", + "Resend Text Succeeded": 'Nous venons d’envoyer le SMS. Merci de répondre "GO" au message que vous avez reçu. Il se peut que cela prenne quelques minutes pour que le message arrive sur votre téléphone.', + "Resend Text Failed": "Il y a eu un problème lors de l’envoi du SMS. Merci de contacter le support si le problème persiste.", + "Password Reset Error": "Il y a eu une error lors de la réinitialisation de votre mot de passe.", + "New Password:": "Nouveau mot de passe:", + "Forgot Password Error": "Votre nom d’utilisateur / adresse email ne peut être trouvé. Merci d’utiliser la même qu’à l’inscription.", + "Forgot Password Success": "Merci de consulter votre boîte mail pour suivre la demande de ‘réinitialisation de mot de passe.", + "Forgot Password Enter Email": "Merci de saisir votre adresse email et nous vous enverrons un lien vous permettant de réinitialiser votre mot de passe :", + "Invite friends": "Inviter vos amis", + "Give $ Get $": "Donnez $10, Recevez $10", + "Give $ Get $ Description": "Chaque ami que vous invitez à Uber recevra $10 de crédits Uber. Dès lors qu’une personne que vous aurez invité aura utilisé Uber pour la première, vous recevrez $10 de crédits Uber également !", + "What are you waiting for?": "N’attendez plus ! Lancez les invitations !", + "Tweet": "Tweeter", + "Invite Link": "Envoyez ce lien par email ou messagerie instantanée à vos amis :", + "Enter Promotion Code": "Si vous avez un code promo, saisissez-le ici:", + "Your Active Promotions": "Vos Codes Promos Actifs", + "Code": "Code", + "Details": "Détails", + "Trips Remaining": "Courses restantes", + "Expires": "Expire", + "No Active Promotions": "Vous n’avez pas de code promo actif.", + "Your Available Promotions": "Votres Promos Disponibles", + "Where do you want us to pick you up?": "Où souhaitez-vous que nous vous prenions en charge ?", + "Address to search": "Adresse à rechercher", + "Search": "Chercher", + "Driver Name:": "Nom du chauffeur:", + "Driver #:": "# Chauffeur:", + "Pickup Address:": "Lieu de prise en charge:", + "Add to Favorite Locations": "Ajoutez aux Lieux Favoris", + "Star": "Étoiles", + "Nickname:": "Pseudo", + "Add": "Ajouter", + "Your last trip": "Votre dernière course", + "Please rate your driver:": "Merci de noter votre chauffeur :", + "Comments: (optional)": "Commentaires: (optionnel)", + "Rate Trip": "Notez votre course", + "Pickup time:": "Heure de Prise en Charge :", + "Miles:": "Kilomètres :", + "Trip time:": "Temps de course :", + "Fare:": "Tarif :", + "Favorite Locations": "Lieux Favoris", + "Search Results": "Résultats", + "You have no favorite locations saved.": "Vous n’avez pas de lieux de prise en charge favoris.", + "Loading...": "Chargement...", + "Request Pickup": "Commander ici", + "Cancel Pickup": "Annuler", + "Requesting Closest Driver": "Nous demandons au chauffeur le plus proche de vous prendre en charge...", + "En Route": "Vous êtes actuellement en route...", + "Rate Last Trip": "Merci de noter votre précédent trajet pour faire une autre course.", + "Rate Before Submitting": "Merci de noter votre trajet avant de le valider.", + "Address too short": "L’adresse est trop courte", + "or did you mean": "ou vouliez-vous dire", + "Search Address Failed": "Impossible de trouver l’adresse spécifiée. Merci de saisir une autre adresse proche de l’endroit où vous vous trouvez.", + "Sending pickup request...": "Envoi de la demande de prise en charge...", + "Cancel Request Prompt": "Voulez-vous vraiment annuler votre demande ?", + "Cancel Request Arrived Prompt": 'Voulez-vous vraiment annuler votre demande ? Votre chauffeur est arrivé, vous serez donc facturé de $10 de frais d’annulation. Il pourrait être utile que vous appeliez votre chauffeur maintenant.', + "Favorite Location Nickname Length Error": "Le pseudo doit faire au moins 3 caractères de long", + "Favorite Location Save Succeeded": "Adresse enregistrée !", + "Favorite Location Save Failed": "Impossible d’enregistrer votre adresse. Merci de réessayer ultérieurement.", + "Favorite Location Title": "Adresse favorie <%= id %>", + "Search Location Title": "Recherche d’adresse <%= id %>", + "ETA Message": "Temps d’attente estimé: environ <%= minutes %> minutes", + "Nearest Cab Message": "Le chauffeur le plus proche sera là dans <%= minutes %> minute(s)", + "Arrival ETA Message": "Votre chauffeur arrivera dans <%= minutes %> minute(s)", + "Arriving Now Message": "Votre chauffeur est en approche...", + "Rating Driver Failed": "Impossible de contacter le serveur. Merci de réessayer ultérieurement ou de contacter le support si le problème persiste.", + "settings": "Paramètres", + "Information": "Information", + "Picture": "Photo", + "Change password": "Modifier votre mot de passe", + "Your current Picture": "Votre photo", + "Your Favorite Locations": "Vos lieux favoris", + "You have no favorite locations saved.": "Vous n’avez pas de lieu favori", + "Account Information": "Informations Personnelles", + "Mobile Phone Information": "Informations de Mobile", + "Change Your Password": "Changez votre mot de passe.", + "Country": "Pays", + "Language": "Langue", + "Favorite Location": "Lieu favori", + "No Approximate Address": "Impossible de trouver une adresse même approximative", + "Address:": "Adresse :", + "Information Update Succeeded": "Vos informations ont été mises à jour !", + "Information Update Failed": "Nous n’avons pas pu mettre à jour vos informations. Merci de réessayer dans quelques instants ou de contacter le support si le problème persiste.", + "Location Delete Succeeded": "Adresse supprimée !", + "Location Delete Failed": "Nous n’avons pas pu supprimée votre adresse favorie. Merci de réessayer plus tard ou de contacter le support si le problème persiste.", + "Location Edit Succeeded": "Modifications sauvegardées !", + "Location Edit Failed": "Nous n’avons pas pu sauvegarder vos modifications. Merci de réessayer dans quelques minutes.", + "Picture Update Succeeded": "Votre photo a été mise à jour !", + "Picture Update Failed": "Nous n’avons pas pu mettre à jour votre photo. Merci de réessayer dans quelques instants.", + "Personal Information": "Informations Personnelles", + "Mobile Phone Number": "Numéro de Téléphone Portable", + "Payment Information": "Informations de Facturation", + "Your card will not be charged until you take a trip.": "Votre carte ne sera pas débitée avant votre premier trajet.", + "Card Number": "Numéro de Carte", + "Promotion Code Input Label": "Code promo, code d’invitation ou “deal” acheté en ligne (optionnel)", + "Terms and Conditions": "Conditions Générales", + "HELP": "HELP", + "STOP": "STOP", + "Sign Up Agreement": "En souscrivant, j’accepte les <%= terms_link %> et <%= privacy_link %> et comprends qu’Uber est un outil de commande de chauffeur, et non un transporteur.", + "Sign Up Agreement Error": "Vous devez accepter les Conditions Générales d’utilisation d’Uber Terms and Conditions et la Politique de Confidentialité pour continuer.", + "Message and Data Rates Disclosure": "Les frais d’envoi de SMS et de consommation de données peuvent s’appliquer. Répondez <%= help_string %> au 827-222 pour obtenir de l’aide. Répondez <%= stop_string %> au 827-222 pour ne plus recevoir de SMS. Pour plus d’aide, visitez support.uber.com ou appelez le (866) 576-1039. Opérateurs supportés: AT&T, Sprint, Verizon, T-Mobile, Orange, SFR et Bouygues Telecom.", + "Zip/Postal Code": "Code Postal", + "Expiration Date": "Date D'expiration", + "Security Code": "Code de Sécurité", + "Type of Card": "Type", + "Personal": "Personnel", + "Business": "Entreprise", + "Promotion Code": "Code Promo", + "Legal Information": "Mentions Légales", + "I Agree": "J'accepte.", + "Your Trip": "Votre Course", + "Trip Info": "Informations de la Course", + "Request a fare review": "Demander un contrôle du tarif", + "Fare Review Submitted": "Votre demande de contrôle du tarif a été soumis. Nous reviendrons vers vous rapidement concernant cette demande. Nous nous excusons pour les dérangements éventuellement occasionnés !", + "Fair Price Consideration": "Nous nous engageons à proposer Uber à un tarif juste. Avant de demander un contrôle du tarif, merci de prendre en compte :", + "Your Fare Calculation": "Calcul du Prix", + "Charges": "Coûts", + "Discounts": "Réductions", + "Total Charge": "Coût total", + "Uber pricing information": "Information sur les prix d’Uber", + "Uber Pricing Information Message": "<%= learn_link %> est disponible sur notre site web.", + "GPS Point Capture Disclosure": "A cause d’un nombre limité de coordonnées GPS sauvegardées, les angles de votre trajet sur la carte peuvent apparaître coupés ou arrondis. Ces légères incohérences débouchent sur des distances mesurées plus courtes, ce qui implique toujours un prix du trajet moins élevé.", + "Fare Review Note": "Merci de nous expliquer pourquoi le tarif de cette course nécessite d’être contrôlé. Vos commentaires ci-dessous nous aideront à établir un prix plus juste si nécessaire :", + "Fare Review Error": "Il y a eu une erreur lors de l’envoi de la demande. Assurez-vous d’avoir bien ajouté une description à votre demande." + }; +}).call(this); +}, "views/clients/billing": function(exports, require, module) {(function() { + var clientsBillingTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + clientsBillingTemplate = require('templates/clients/billing'); + exports.ClientsBillingView = (function() { + __extends(ClientsBillingView, UberView); + function ClientsBillingView() { + ClientsBillingView.__super__.constructor.apply(this, arguments); + } + ClientsBillingView.prototype.id = 'billing_view'; + ClientsBillingView.prototype.className = 'view_container'; + ClientsBillingView.prototype.events = { + 'click a#add_card': 'addCard', + 'click .charge_arrear': 'chargeArrear' + }; + ClientsBillingView.prototype.render = function() { + this.RefreshUserInfo(__bind(function() { + var cards, newForm; + this.HideSpinner(); + $(this.el).html(clientsBillingTemplate()); + if (USER.payment_gateway.payment_profiles.length === 0) { + newForm = new app.views.clients.modules.creditcard; + $(this.el).find("#add_card_wrapper").html(newForm.render(0).el); + } else { + cards = new app.views.clients.modules.creditcard; + $("#cards").html(cards.render("all").el); + } + return this.delegateEvents(); + }, this)); + return this; + }; + ClientsBillingView.prototype.addCard = function(e) { + var newCard; + e.preventDefault(); + newCard = new app.views.clients.modules.creditcard; + $('#cards').append(newCard.render("new").el); + return $("a#add_card").hide(); + }; + ClientsBillingView.prototype.chargeArrear = function(e) { + var $el, arrearId, attrs, cardId, options, tryCharge; + e.preventDefault(); + $(".error_message").text(""); + $el = $(e.currentTarget); + arrearId = $el.attr('id'); + cardId = $el.parent().find('#card_to_charge').val(); + this.ShowSpinner('submit'); + tryCharge = new app.models.clientbills({ + id: arrearId + }); + attrs = { + payment_profile_id: cardId, + dataType: 'json' + }; + options = { + success: __bind(function(data, textStatus, jqXHR) { + $el.parent().find(".success_message").text(t("Billing Succeeded")); + $el.hide(); + return $el.parent().find('#card_to_charge').hide(); + }, this), + error: __bind(function(jqXHR, status, errorThrown) { + return $el.parent().find(".error_message").text(JSON.parse(status.responseText).error); + }, this), + complete: __bind(function() { + return this.HideSpinner(); + }, this) + }; + return tryCharge.save(attrs, options); + }; + return ClientsBillingView; + })(); +}).call(this); +}, "views/clients/confirm_email": function(exports, require, module) {(function() { + var clientsConfirmEmailTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + clientsConfirmEmailTemplate = require('templates/clients/confirm_email'); + exports.ClientsConfirmEmailView = (function() { + __extends(ClientsConfirmEmailView, UberView); + function ClientsConfirmEmailView() { + ClientsConfirmEmailView.__super__.constructor.apply(this, arguments); + } + ClientsConfirmEmailView.prototype.id = 'confirm_email_view'; + ClientsConfirmEmailView.prototype.className = 'view_container'; + ClientsConfirmEmailView.prototype.render = function(token) { + var attrs; + $(this.el).html(clientsConfirmEmailTemplate()); + attrs = { + data: { + email_token: token + }, + success: __bind(function(data, textStatus, jqXHR) { + var show_dashboard; + this.HideSpinner(); + show_dashboard = function() { + return app.routers.clients.navigate('!/dashboard', true); + }; + if (data.status === 'OK') { + $('.success_message').show(); + return _.delay(show_dashboard, 3000); + } else if (data.status === 'ALREADY_COMFIRMED') { + $('.already_confirmed_message').show(); + return _.delay(show_dashboard, 3000); + } else { + return $('.error_message').show(); + } + }, this), + error: __bind(function(e) { + this.HideSpinner(); + return $('.error_message').show(); + }, this), + complete: function(status) { + return $('#attempt_text').hide(); + }, + dataType: 'json', + type: 'PUT', + url: "" + API + "/users/self" + }; + $.ajax(attrs); + this.ShowSpinner('submit'); + return this; + }; + return ClientsConfirmEmailView; + })(); +}).call(this); +}, "views/clients/dashboard": function(exports, require, module) {(function() { + var clientsDashboardTemplate; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + clientsDashboardTemplate = require('templates/clients/dashboard'); + exports.ClientsDashboardView = (function() { + var displayFirstTrip; + __extends(ClientsDashboardView, UberView); + function ClientsDashboardView() { + this.showAllTrips = __bind(this.showAllTrips, this); + this.render = __bind(this.render, this); + ClientsDashboardView.__super__.constructor.apply(this, arguments); + } + ClientsDashboardView.prototype.id = 'dashboard_view'; + ClientsDashboardView.prototype.className = 'view_container'; + ClientsDashboardView.prototype.events = { + 'click a.confirmation': 'confirmationClick', + 'click #resend_email': 'resendEmail', + 'click #resend_mobile': 'resendMobile', + 'click #show_all_trips': 'showAllTrips' + }; + ClientsDashboardView.prototype.render = function() { + var displayPage, downloadTrips; + this.HideSpinner(); + displayPage = __bind(function() { + $(this.el).html(clientsDashboardTemplate()); + this.confirmationsSetup(); + return this.RequireMaps(__bind(function() { + if (USER.trips.models[0]) { + if (!USER.trips.models[0].get("points")) { + return USER.trips.models[0].fetch({ + data: { + relationships: 'points' + }, + success: __bind(function() { + this.CacheData("USERtrips", USER.trips); + return displayFirstTrip(); + }, this) + }); + } else { + return displayFirstTrip(); + } + } + }, this)); + }, this); + downloadTrips = __bind(function() { + return this.DownloadUserTrips(displayPage, false, 10); + }, this); + this.RefreshUserInfo(downloadTrips); + return this; + }; + displayFirstTrip = __bind(function() { + var bounds, endPos, map, myOptions, path, polyline, startPos; + myOptions = { + zoom: 12, + mapTypeId: google.maps.MapTypeId.ROADMAP, + zoomControl: false, + rotateControl: false, + panControl: false, + mapTypeControl: false, + scrollwheel: false + }; + if (USER.trips.length === 10) { + $("#show_all_trips").show(); + } + if (USER.trips.length > 0) { + map = new google.maps.Map(document.getElementById("trip_details_map"), myOptions); + bounds = new google.maps.LatLngBounds(); + path = []; + _.each(USER.trips.models[0].get('points'), __bind(function(point) { + path.push(new google.maps.LatLng(point.lat, point.lng)); + return bounds.extend(_.last(path)); + }, this)); + map.fitBounds(bounds); + startPos = new google.maps.Marker({ + position: _.first(path), + map: map, + title: t('Trip started here'), + icon: 'https://uber-static.s3.amazonaws.com/marker_start.png' + }); + endPos = new google.maps.Marker({ + position: _.last(path), + map: map, + title: t('Trip ended here'), + icon: 'https://uber-static.s3.amazonaws.com/marker_end.png' + }); + polyline = new google.maps.Polyline({ + path: path, + strokeColor: '#003F87', + strokeOpacity: 1, + strokeWeight: 5 + }); + return polyline.setMap(map); + } + }, ClientsDashboardView); + ClientsDashboardView.prototype.confirmationsSetup = function() { + var blink, cardForm, element, _ref, _ref2, _ref3, _ref4, _ref5; + blink = function(element) { + var opacity; + opacity = 0.5; + if (element.css('opacity') === "0.5") { + opacity = 1.0; + } + return element.fadeTo(2000, opacity, function() { + return blink(element); + }); + }; + if (((_ref = window.USER) != null ? (_ref2 = _ref.payment_gateway) != null ? (_ref3 = _ref2.payment_profiles) != null ? _ref3.length : void 0 : void 0 : void 0) === 0) { + element = $('#confirmed_credit_card'); + cardForm = new app.views.clients.modules.creditcard; + $('#card.info').append(cardForm.render().el); + blink(element); + } + if (((_ref4 = window.USER) != null ? _ref4.confirm_email : void 0) === false) { + element = $('#confirmed_email'); + blink(element); + } + if ((((_ref5 = window.USER) != null ? _ref5.confirm_mobile : void 0) != null) === false) { + element = $('#confirmed_mobile'); + return blink(element); + } + }; + ClientsDashboardView.prototype.confirmationClick = function(e) { + e.preventDefault(); + $('.info').hide(); + $('#more_info').show(); + switch (e.currentTarget.id) { + case "card": + return $('#card.info').slideToggle(); + case "mobile": + return $('#mobile.info').slideToggle(); + case "email": + return $('#email.info').slideToggle(); + } + }; + ClientsDashboardView.prototype.resendEmail = function(e) { + var $el; + e.preventDefault(); + $el = $(e.currentTarget); + $el.removeAttr('href').prop({ + disabled: true + }); + $el.html(t("Sending Email")); + return $.ajax({ + type: 'GET', + url: API + '/users/request_confirm_email', + data: { + token: USER.token + }, + dataType: 'json', + success: __bind(function(data, textStatus, jqXHR) { + return $el.html(t("Resend Email Succeeded")); + }, this), + error: __bind(function(jqXHR, textStatus, errorThrown) { + return $el.html(t("Resend Email Failed")); + }, this) + }); + }; + ClientsDashboardView.prototype.resendMobile = function(e) { + var $el; + e.preventDefault(); + $el = $(e.currentTarget); + $el.removeAttr('href').prop({ + disabled: true + }); + $el.html("Sending message..."); + return $.ajax({ + type: 'GET', + url: API + '/users/request_confirm_mobile', + data: { + token: USER.token + }, + dataType: 'json', + success: __bind(function(data, textStatus, jqXHR) { + return $el.html(t("Resend Text Succeeded")); + }, this), + error: __bind(function(jqXHR, textStatus, errorThrown) { + return $el.html(t("Resend Text Failed")); + }, this) + }); + }; + ClientsDashboardView.prototype.showAllTrips = function(e) { + e.preventDefault(); + $(e.currentTarget).hide(); + return this.DownloadUserTrips(this.render, true, 1000); + }; + return ClientsDashboardView; + }).call(this); +}).call(this); +}, "views/clients/forgot_password": function(exports, require, module) {(function() { + var clientsForgotPasswordTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + clientsForgotPasswordTemplate = require('templates/clients/forgot_password'); + exports.ClientsForgotPasswordView = (function() { + __extends(ClientsForgotPasswordView, UberView); + function ClientsForgotPasswordView() { + ClientsForgotPasswordView.__super__.constructor.apply(this, arguments); + } + ClientsForgotPasswordView.prototype.id = 'forgotpassword_view'; + ClientsForgotPasswordView.prototype.className = 'view_container modal_view_container'; + ClientsForgotPasswordView.prototype.events = { + "submit #password_reset": "passwordReset", + "click #password_reset_submit": "passwordReset", + "submit #forgot_password": "forgotPassword", + "click #forgot_password_submit": "forgotPassword" + }; + ClientsForgotPasswordView.prototype.render = function(token) { + this.HideSpinner(); + $(this.el).html(clientsForgotPasswordTemplate({ + token: token + })); + this.delegateEvents(); + return this; + }; + ClientsForgotPasswordView.prototype.forgotPassword = function(e) { + var attrs; + e.preventDefault(); + $('.success_message').hide(); + $(".error_message").hide(); + attrs = { + data: { + login: $("#login").val() + }, + success: __bind(function(data, textStatus, jqXHR) { + this.HideSpinner(); + $('.success_message').show(); + return $("#forgot_password").hide(); + }, this), + error: __bind(function(e) { + this.HideSpinner(); + return $('.error_message').show(); + }, this), + dataType: 'json', + type: 'PUT', + url: "" + API + "/users/forgot_password" + }; + $.ajax(attrs); + return this.ShowSpinner('submit'); + }; + ClientsForgotPasswordView.prototype.passwordReset = function(e) { + var attrs; + e.preventDefault(); + attrs = { + data: { + email_token: $("#token").val(), + password: $("#password").val() + }, + success: __bind(function(data, textStatus, jqXHR) { + this.HideSpinner(); + $.cookie('token', data.token); + amplify.store('USERjson', data); + app.refreshMenu(); + return location.hash = '!/dashboard'; + }, this), + error: __bind(function(e) { + this.HideSpinner(); + return $('#error_reset').show(); + }, this), + dataType: 'json', + type: 'PUT', + url: "" + API + "/users/self" + }; + $.ajax(attrs); + return this.ShowSpinner('submit'); + }; + return ClientsForgotPasswordView; + })(); +}).call(this); +}, "views/clients/invite": function(exports, require, module) {(function() { + var clientsInviteTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + clientsInviteTemplate = require('templates/clients/invite'); + exports.ClientsInviteView = (function() { + __extends(ClientsInviteView, UberView); + function ClientsInviteView() { + ClientsInviteView.__super__.constructor.apply(this, arguments); + } + ClientsInviteView.prototype.id = 'invite_view'; + ClientsInviteView.prototype.className = 'view_container'; + ClientsInviteView.prototype.render = function() { + this.ReadUserInfo(); + this.HideSpinner(); + $(this.el).html(clientsInviteTemplate()); + console.log(screen); + return this; + }; + return ClientsInviteView; + })(); +}).call(this); +}, "views/clients/login": function(exports, require, module) {(function() { + var clientsLoginTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + clientsLoginTemplate = require('templates/clients/login'); + exports.ClientsLoginView = (function() { + __extends(ClientsLoginView, UberView); + function ClientsLoginView() { + ClientsLoginView.__super__.constructor.apply(this, arguments); + } + ClientsLoginView.prototype.id = 'login_view'; + ClientsLoginView.prototype.className = 'view_container modal_view_container'; + ClientsLoginView.prototype.events = { + 'submit form': 'authenticate', + 'click button': 'authenticate' + }; + ClientsLoginView.prototype.initialize = function() { + _.bindAll(this, 'render'); + return this.render(); + }; + ClientsLoginView.prototype.render = function() { + this.HideSpinner(); + $(this.el).html(clientsLoginTemplate()); + this.delegateEvents(); + return this.place(); + }; + ClientsLoginView.prototype.authenticate = function(e) { + e.preventDefault(); + return $.ajax({ + type: 'POST', + url: API + '/auth/web_login/client', + data: { + login: $("#login").val(), + password: $("#password").val() + }, + dataType: 'json', + success: function(data, textStatus, jqXHR) { + $.cookie('user', JSON.stringify(data)); + $.cookie('token', data.token); + amplify.store('USERjson', data); + $('header').html(app.views.shared.menu.render().el); + return app.routers.clients.navigate('!/dashboard', true); + }, + error: function(jqXHR, textStatus, errorThrown) { + $.cookie('user', null); + $.cookie('token', null); + if (jqXHR.status === 403) { + $.cookie('redirected_user', JSON.stringify(JSON.parse(jqXHR.responseText).error_obj), { + domain: '.uber.com' + }); + window.location = 'http://partners.uber.com/'; + } + return $('.error_message').html(JSON.parse(jqXHR.responseText).error).hide().fadeIn(); + } + }); + }; + return ClientsLoginView; + })(); +}).call(this); +}, "views/clients/modules/credit_card": function(exports, require, module) {(function() { + var creditCardTemplate; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + creditCardTemplate = require('templates/clients/modules/credit_card'); + exports.CreditCardView = (function() { + __extends(CreditCardView, UberView); + function CreditCardView() { + CreditCardView.__super__.constructor.apply(this, arguments); + } + CreditCardView.prototype.id = 'creditcard_view'; + CreditCardView.prototype.className = 'module_container'; + CreditCardView.prototype.events = { + 'submit #credit_card_form': 'processNewCard', + 'click #new_card': 'processNewCard', + 'change #card_number': 'showCardType', + 'click .edit_card_show': 'showEditCard', + 'click .edit_card': 'editCard', + 'click .delete_card': 'deleteCard', + 'click .make_default': 'makeDefault', + 'change .use_case': 'saveUseCase' + }; + CreditCardView.prototype.initialize = function() { + return app.collections.paymentprofiles.bind("refresh", __bind(function() { + return this.RefreshUserInfo(__bind(function() { + this.render("all"); + return this.HideSpinner(); + }, this)); + }, this)); + }; + CreditCardView.prototype.render = function(cards) { + if (cards == null) { + cards = "new"; + } + if (cards === "all") { + app.collections.paymentprofiles.reset(USER.payment_gateway.payment_profiles); + cards = app.collections.paymentprofiles; + } + $(this.el).html(creditCardTemplate({ + cards: cards + })); + return this; + }; + CreditCardView.prototype.processNewCard = function(e) { + var $el, attrs, model, options; + e.preventDefault(); + this.ClearGlobalStatus(); + $el = $("#credit_card_form"); + $el.find('.error_message').html(""); + attrs = { + card_number: $el.find('#card_number').val(), + card_code: $el.find('#card_code').val(), + card_expiration_month: $el.find('#card_expiration_month').val(), + card_expiration_year: $el.find('#card_expiration_year').val(), + use_case: $el.find('#use_case').val(), + "default": $el.find('#default_check').prop("checked") + }; + options = { + statusCode: { + 200: __bind(function(e) { + this.HideSpinner(); + $('#cc_form_wrapper').hide(); + app.collections.paymentprofiles.trigger("refresh"); + $(this.el).remove(); + $("a#add_card").show(); + return $('section').html(app.views.clients.billing.render().el); + }, this), + 406: __bind(function(e) { + var error, errors, _i, _len, _ref, _results; + this.HideSpinner(); + errors = JSON.parse(e.responseText); + _ref = _.keys(errors); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + error = _ref[_i]; + _results.push(error === "top_of_form" ? $("#top_of_form").html(errors[error]) : $("#credit_card_form").find("#" + error).parent().find(".error_message").html(errors[error])); + } + return _results; + }, this), + 420: __bind(function(e) { + this.HideSpinner(); + return $("#top_of_form").html(t("Unable to Verify Card")); + }, this) + } + }; + this.ShowSpinner("submit"); + model = new app.models.paymentprofile; + model.save(attrs, options); + return app.collections.paymentprofiles.add(model); + }; + CreditCardView.prototype.showCardType = function(e) { + var $el, reAmerica, reDiscover, reMaster, reVisa, validCard; + reVisa = /^4\d{3}-?\d{4}-?\d{4}-?\d{4}$/; + reMaster = /^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/; + reAmerica = /^6011-?\d{4}-?\d{4}-?\d{4}$/; + reDiscover = /^3[4,7]\d{13}$/; + $el = $("#card_logos"); + validCard = false; + if (e.currentTarget.value.match(reVisa)) { + validCard = true; + } else if (e.currentTarget.value.match(reMaster)) { + $el.css('background-position', "-60px"); + validCard = true; + } else if (e.currentTarget.value.match(reAmerica)) { + $el.css('background-position', "-120px"); + validCard = true; + } else if (e.currentTarget.value.match(reDiscover)) { + $el.css('background-position', "-180px"); + validCard = true; + } + if (validCard) { + $el.css('width', "60px"); + return $el.css('margin-left', "180px"); + } else { + $el.css('width', "250px"); + return $el.css('margin-left', "80px"); + } + }; + CreditCardView.prototype.showEditCard = function(e) { + var $el, id; + e.preventDefault(); + $el = $(e.currentTarget); + if ($el.html() === t("Edit")) { + id = $el.html(t("Cancel")).parents("tr").attr("id").substring(1); + return $("#e" + id).show(); + } else { + id = $el.html(t("Edit")).parents("tr").attr("id").substring(1); + return $("#e" + id).hide(); + } + }; + CreditCardView.prototype.editCard = function(e) { + var $el, attrs, id, options; + e.preventDefault(); + this.ClearGlobalStatus(); + $el = $(e.currentTarget).parents("td"); + id = $el.parents("tr").attr("id").substring(1); + $el.attr('disabled', 'disabled'); + this.ShowSpinner('submit'); + attrs = { + card_expiration_month: $el.find('#card_expiration_month').val(), + card_expiration_year: $el.find('#card_expiration_year').val(), + card_code: $el.find('#card_code').val() + }; + options = { + success: __bind(function(response) { + this.HideSpinner(); + this.ShowSuccess(t("Credit Card Update Succeeded")); + $("#e" + id).hide(); + $("#d" + id).find(".edit_card_show").html(t("Edit")); + return app.collections.paymentprofiles.trigger("refresh"); + }, this), + error: __bind(function(e) { + this.HideSpinner(); + this.ShowError(t("Credit Card Update Failed")); + return $el.removeAttr('disabled'); + }, this) + }; + app.collections.paymentprofiles.models[id].set(attrs); + return app.collections.paymentprofiles.models[id].save({}, options); + }; + CreditCardView.prototype.deleteCard = function(e) { + var $el, id, options; + e.preventDefault(); + $el = $(e.currentTarget).parents("td"); + id = $el.parents("tr").attr("id").substring(1); + this.ClearGlobalStatus(); + this.ShowSpinner('submit'); + options = { + success: __bind(function(response) { + this.ShowSuccess(t("Credit Card Delete Succeeded")); + $("form").hide(); + app.collections.paymentprofiles.trigger("refresh"); + return $('section').html(app.views.clients.billing.render().el); + }, this), + error: __bind(function(xhr, e) { + this.HideSpinner(); + return this.ShowError(t("Credit Card Delete Failed")); + }, this) + }; + return app.collections.paymentprofiles.models[id].destroy(options); + }; + CreditCardView.prototype.saveUseCase = function(e) { + var $el, attrs, id, options, use_case; + this.ClearGlobalStatus(); + $el = $(e.currentTarget); + use_case = $el.val(); + id = $el.parents("tr").attr("id").substring(1); + attrs = { + use_case: use_case + }; + options = { + success: __bind(function(response) { + return this.ShowSuccess(t("Credit Card Update Category Succeeded")); + }, this), + error: __bind(function(e) { + return this.ShowError(t("Credit Card Update Category Failed")); + }, this) + }; + app.collections.paymentprofiles.models[id].set(attrs); + return app.collections.paymentprofiles.models[id].save({}, options); + }; + CreditCardView.prototype.makeDefault = function(e) { + var $el, attrs, id, options; + e.preventDefault(); + this.ClearGlobalStatus(); + $el = $(e.currentTarget).parents("td"); + id = $el.parents("tr").attr("id").substring(1); + attrs = { + "default": true + }; + options = { + success: __bind(function(response) { + this.ShowSuccess(t("Credit Card Update Default Succeeded")); + return app.collections.paymentprofiles.trigger("refresh"); + }, this), + error: __bind(function(e) { + return this.ShowError(t("Credit Card Update Default Failed")); + }, this) + }; + app.collections.paymentprofiles.models[id].set(attrs); + return app.collections.paymentprofiles.models[id].save({}, options); + }; + return CreditCardView; + })(); +}).call(this); +}, "views/clients/promotions": function(exports, require, module) {(function() { + var clientsPromotionsTemplate; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + clientsPromotionsTemplate = require('templates/clients/promotions'); + exports.ClientsPromotionsView = (function() { + __extends(ClientsPromotionsView, UberView); + function ClientsPromotionsView() { + this.render = __bind(this.render, this); + ClientsPromotionsView.__super__.constructor.apply(this, arguments); + } + ClientsPromotionsView.prototype.id = 'promotions_view'; + ClientsPromotionsView.prototype.className = 'view_container'; + ClientsPromotionsView.prototype.events = { + 'submit form': 'submitPromo', + 'click button': 'submitPromo' + }; + ClientsPromotionsView.prototype.initialize = function() { + if (this.model) { + return this.RefreshUserInfo(this.render); + } + }; + ClientsPromotionsView.prototype.render = function() { + var renderTemplate; + this.ReadUserInfo(); + renderTemplate = __bind(function() { + $(this.el).html(clientsPromotionsTemplate({ + promos: window.USER.unexpired_client_promotions || [] + })); + return this.HideSpinner(); + }, this); + this.DownloadUserPromotions(renderTemplate); + return this; + }; + ClientsPromotionsView.prototype.submitPromo = function(e) { + var attrs, model, options, refreshTable; + e.preventDefault(); + this.ClearGlobalStatus(); + refreshTable = __bind(function() { + $('section').html(this.render().el); + return this.HideSpinner(); + }, this); + attrs = { + code: $('#code').val() + }; + options = { + success: __bind(function(response) { + this.HideSpinner(); + if (response.get('first_name')) { + return this.ShowSuccess("Your promotion has been applied in the form of an account credit. Click here to check your balance."); + } else { + this.ShowSuccess("Your promotion has successfully been applied"); + return this.RefreshUserInfo(this.render, true); + } + }, this), + statusCode: { + 400: __bind(function(e) { + this.ShowError(JSON.parse(e.responseText).error); + return this.HideSpinner(); + }, this) + } + }; + this.ShowSpinner("submit"); + model = new app.models.promotions; + return model.save(attrs, options); + }; + return ClientsPromotionsView; + })(); +}).call(this); +}, "views/clients/request": function(exports, require, module) {(function() { + var clientsRequestTemplate; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + clientsRequestTemplate = require('templates/clients/request'); + exports.ClientsRequestView = (function() { + __extends(ClientsRequestView, UberView); + function ClientsRequestView() { + this.AjaxCall = __bind(this.AjaxCall, this); + this.AskDispatch = __bind(this.AskDispatch, this); + this.removeMarkers = __bind(this.removeMarkers, this); + this.displaySearchLoc = __bind(this.displaySearchLoc, this); + this.displayFavLoc = __bind(this.displayFavLoc, this); + this.showFavLoc = __bind(this.showFavLoc, this); + this.addToFavLoc = __bind(this.addToFavLoc, this); + this.removeCabs = __bind(this.removeCabs, this); + this.requestRide = __bind(this.requestRide, this); + this.rateTrip = __bind(this.rateTrip, this); + this.locationChange = __bind(this.locationChange, this); + this.panToLocation = __bind(this.panToLocation, this); + this.clickLocation = __bind(this.clickLocation, this); + this.searchLocation = __bind(this.searchLocation, this); + this.mouseoutLocation = __bind(this.mouseoutLocation, this); + this.mouseoverLocation = __bind(this.mouseoverLocation, this); + this.fetchTripDetails = __bind(this.fetchTripDetails, this); + this.submitRating = __bind(this.submitRating, this); + this.setStatus = __bind(this.setStatus, this); + this.initialize = __bind(this.initialize, this); + ClientsRequestView.__super__.constructor.apply(this, arguments); + } + ClientsRequestView.prototype.id = 'request_view'; + ClientsRequestView.prototype.className = 'view_container'; + ClientsRequestView.prototype.pollInterval = 2 * 1000; + ClientsRequestView.prototype.events = { + "submit #search_form": "searchAddress", + "click .locations_link": "locationLinkHandle", + "mouseover .location_row": "mouseoverLocation", + "mouseout .location_row": "mouseoutLocation", + "click .location_row": "clickLocation", + "click #search_location": "searchLocation", + "click #pickupHandle": "pickupHandle", + "click .stars": "rateTrip", + "submit #rating_form": "submitRating", + "click #addToFavButton": "showFavLoc", + "click #favLocNickname": "selectInputText", + "submit #favLoc_form": "addToFavLoc" + }; + ClientsRequestView.prototype.status = ""; + ClientsRequestView.prototype.pickupMarker = "https://uber-static.s3.amazonaws.com/pickup_marker.png"; + ClientsRequestView.prototype.cabMarker = "https://uber-static.s3.amazonaws.com/cab_marker.png"; + ClientsRequestView.prototype.initialize = function() { + var displayCabs; + displayCabs = __bind(function() { + return this.AskDispatch("NearestCab"); + }, this); + this.showCabs = _.throttle(displayCabs, this.pollInterval); + return this.numSearchToDisplay = 1; + }; + ClientsRequestView.prototype.setStatus = function(status) { + var autocomplete; + if (this.status === status) { + return; + } + try { + google.maps.event.trigger(this.map, 'resize'); + } catch (_e) {} + switch (status) { + case "init": + this.AskDispatch("StatusClient"); + this.status = "init"; + return this.ShowSpinner("load"); + case "ready": + this.HideSpinner(); + $(".panel").hide(); + $("#top_bar").fadeIn(); + $("#location_panel").fadeIn(); + $("#location_panel_control").fadeIn(); + $("#pickupHandle").attr("class", "button_green").fadeIn().find("span").html(t("Request Pickup")); + this.pickup_icon.setDraggable(true); + this.map.panTo(this.pickup_icon.getPosition()); + this.showCabs(); + try { + this.pickup_icon.setMap(this.map); + this.displayFavLoc(); + autocomplete = new google.maps.places.Autocomplete(document.getElementById('address'), { + types: ['geocode'] + }); + autocomplete.bindTo('bounds', this.map); + } catch (_e) {} + return this.status = "ready"; + case "searching": + this.HideSpinner(); + this.removeMarkers(); + $(".panel").hide(); + $("#top_bar").fadeOut(); + $("#status_message").html(t("Requesting Closest Driver")); + $("#pickupHandle").attr("class", "button_red").fadeIn().find("span").html(t("Cancel Pickup")); + this.pickup_icon.setDraggable(false); + this.pickup_icon.setMap(this.map); + return this.status = "searching"; + case "waiting": + this.HideSpinner(); + this.removeMarkers(); + $(".panel").hide(); + $("#top_bar").fadeOut(); + $("#pickupHandle").attr("class", "button_red").fadeIn().find("span").html(t("Cancel Pickup")); + $("#waiting_riding").fadeIn(); + this.pickup_icon.setDraggable(false); + this.pickup_icon.setMap(this.map); + return this.status = "waiting"; + case "arriving": + this.HideSpinner(); + this.removeMarkers(); + $(".panel").hide(); + $("#top_bar").fadeOut(); + $("#pickupHandle").attr("class", "button_red").fadeIn().find("span").html(t("Cancel Pickup")); + $("#waiting_riding").fadeIn(); + this.pickup_icon.setDraggable(false); + this.pickup_icon.setMap(this.map); + return this.status = "arriving"; + case "riding": + this.HideSpinner(); + this.removeMarkers(); + $(".panel").hide(); + $("#top_bar").fadeOut(); + $("#pickupHandle").fadeIn().attr("class", "button_red").find("span").html(t("Cancel Pickup")); + $("#waiting_riding").fadeIn(); + this.pickup_icon.setDraggable(false); + this.status = "riding"; + return $("#status_message").html(t("En Route")); + case "rate": + this.HideSpinner(); + $(".panel").hide(); + $("#pickupHandle").fadeOut(); + $("#trip_completed_panel").fadeIn(); + $('#status_message').html(t("Rate Last Trip")); + return this.status = "rate"; + } + }; + ClientsRequestView.prototype.render = function() { + this.ReadUserInfo(); + this.HideSpinner(); + this.ShowSpinner("load"); + $(this.el).html(clientsRequestTemplate()); + this.cabs = []; + this.RequireMaps(__bind(function() { + var center, myOptions, streetViewPano; + center = new google.maps.LatLng(37.7749295, -122.4194155); + this.markers = []; + this.pickup_icon = new google.maps.Marker({ + position: center, + draggable: true, + clickable: true, + icon: this.pickupMarker + }); + this.geocoder = new google.maps.Geocoder(); + myOptions = { + zoom: 12, + center: center, + mapTypeId: google.maps.MapTypeId.ROADMAP, + rotateControl: false, + rotateControl: false, + panControl: false + }; + this.map = new google.maps.Map($(this.el).find("#map_wrapper_right")[0], myOptions); + if (this.status === "ready") { + this.pickup_icon.setMap(this.map); + } + if (geo_position_js.init()) { + geo_position_js.getCurrentPosition(__bind(function(data) { + var location; + location = new google.maps.LatLng(data.coords.latitude, data.coords.longitude); + this.pickup_icon.setPosition(location); + this.map.panTo(location); + return this.map.setZoom(16); + }, this)); + } + this.setStatus("init"); + streetViewPano = this.map.getStreetView(); + google.maps.event.addListener(streetViewPano, 'visible_changed', __bind(function() { + if (streetViewPano.getVisible()) { + this.pickupMarker = "https://uber-static.s3.amazonaws.com/pickup_marker_large.png"; + this.cabMarker = "https://uber-static.s3.amazonaws.com/cab_marker_large.png"; + } else { + this.pickupMarker = "https://uber-static.s3.amazonaws.com/pickup_marker.png"; + this.cabMarker = "https://uber-static.s3.amazonaws.com/cab_marker.png"; + } + this.pickup_icon.setIcon(this.pickupMarker); + return _.each(this.cabs, __bind(function(cab) { + return cab.setIcon(this.cabMarker); + }, this)); + }, this)); + if (this.status === "ready") { + return this.displayFavLoc(); + } + }, this)); + return this; + }; + ClientsRequestView.prototype.submitRating = function(e) { + var $el, message, rating; + e.preventDefault(); + $el = $(e.currentTarget); + rating = 0; + _(5).times(function(num) { + if ($el.find(".stars#" + (num + 1)).attr("src") === "/web/img/star_active.png") { + return rating = num + 1; + } + }); + if (rating === 0) { + $("#status_message").html("").html(t("Rate Before Submitting")); + } else { + this.ShowSpinner("submit"); + this.AskDispatch("RatingDriver", { + rating: rating + }); + } + message = $el.find("#comments").val().toString(); + if (message.length > 5) { + return this.AskDispatch("Feedback", { + message: message + }); + } + }; + ClientsRequestView.prototype.fetchTripDetails = function(id) { + var trip; + trip = new app.models.trip({ + id: id + }); + return trip.fetch({ + data: { + relationships: 'points,driver,city' + }, + dataType: 'json', + success: __bind(function() { + var bounds, endPos, path, polyline, startPos; + bounds = new google.maps.LatLngBounds(); + path = []; + _.each(trip.get('points'), __bind(function(point) { + path.push(new google.maps.LatLng(point.lat, point.lng)); + return bounds.extend(_.last(path)); + }, this)); + startPos = new google.maps.Marker({ + position: _.first(path), + map: this.map, + title: t("Trip started here"), + icon: 'https://uber-static.s3.amazonaws.com/carstart.png' + }); + endPos = new google.maps.Marker({ + position: _.last(path), + map: this.map, + title: t("Trip ended here"), + icon: 'https://uber-static.s3.amazonaws.com/carstop.png' + }); + polyline = new google.maps.Polyline({ + path: path, + strokeColor: '#003F87', + strokeOpacity: 1, + strokeWeight: 5 + }); + polyline.setMap(this.map); + this.map.fitBounds(bounds); + $("#tripTime").html(app.helpers.parseDateTime(trip.get('pickup_local_time'), trip.get('city.timezone'))); + $("#tripDist").html(app.helpers.RoundNumber(trip.get('distance'), 2)); + $("#tripDur").html(app.helpers.FormatSeconds(trip.get('duration'))); + return $("#tripFare").html(app.helpers.FormatCurrency(trip.get('fare'))); + }, this) + }); + }; + ClientsRequestView.prototype.searchAddress = function(e) { + var $locationsDiv, address, alphabet, bounds, showResults; + alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + try { + e.preventDefault(); + } catch (_e) {} + $('.error_message').html(""); + $locationsDiv = $("
"); + address = $('#address').val(); + bounds = new google.maps.LatLngBounds(); + if (address.length < 5) { + $('#status_message').html(t("Address too short")).fadeIn(); + return false; + } + showResults = __bind(function(address, index) { + var first_cell, row, second_cell; + if (index < this.numSearchToDisplay) { + first_cell = "
" + address.formatted_address + "
" + (t('or did you mean')) + "
" + address.formatted_address + "