Mercurial > browser-couch
changeset 55:aa013581aeca
Directory restructuring.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Wed, 15 Apr 2009 08:07:42 -0700 |
parents | 3607a6844691 |
children | dccd8df9005f |
files | big.html big.js browser-couch.js css/index.css index.css index.html js/big.js js/browser-couch.js js/ext/json2.js js/tests.js js/worker-map-reducer.js json2.js tests.js worker-map-reducer.js |
diffstat | 14 files changed, 1480 insertions(+), 1480 deletions(-) [+] |
line wrap: on
line diff
--- a/big.html Wed Apr 15 08:01:14 2009 -0700 +++ b/big.html Wed Apr 15 08:07:42 2009 -0700 @@ -6,7 +6,7 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <link rel="stylesheet" type="text/css" media="all" - href="index.css" /> + href="css/index.css" /> <title>Browser Big Couch Test</title> </head> <body> @@ -18,6 +18,6 @@ <div id="result" style="font-family: monospace;"> </div> </body> -<script src="browser-couch.js"></script> -<script src="big.js"></script> +<script src="js/browser-couch.js"></script> +<script src="js/big.js"></script> </html>
--- a/big.js Wed Apr 15 08:01:14 2009 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,156 +0,0 @@ -var MAX_WORD_LENGTH = 10; -var LEXICON_SIZE = 200; -var MIN_DOCUMENT_LENGTH = 250; -var MAX_DOCUMENT_LENGTH = 500; -var CORPUS_SIZE = 1000; -var UI_LOCK_LIMIT = 100; -var UI_BREATHE_TIME = 10; - -// Returns a random integer between min and max -// Using Math.round() will give you a non-uniform distribution! -function getRandomInt(min, max) -{ - return Math.floor(Math.random() * (max - min + 1)) + min; -} - -function makeRandomWord() { - var word = ""; - var len = getRandomInt(1, MAX_WORD_LENGTH); - for (var i = 0; i < len; i++) { - var charCode = getRandomInt("a".charCodeAt(0), - "z".charCodeAt(0)); - var letter = String.fromCharCode(charCode); - word += letter; - } - return word; -} - -function makeLexicon() { - var lexicon = []; - - for (var i = 0; i < LEXICON_SIZE; i++) - lexicon.push(makeRandomWord()); - - return lexicon; -} - -function makeDocument(lexicon) { - var len = getRandomInt(MIN_DOCUMENT_LENGTH, - MAX_DOCUMENT_LENGTH); - var doc = []; - - for (var i = 0; i < len; i++) { - var wordIndex = getRandomInt(0, lexicon.length); - doc.push(lexicon[wordIndex]); - } - - return doc.join(" "); -} - -function makeCorpus(db, progress, chunkSize, cb) { - var lexicon = makeLexicon(); - var docs = []; - var i = 0; - - function makeNextDocument() { - var iAtStart = i; - - do { - docs.push({id: i, - content: makeDocument(lexicon)}); - i += 1; - } while (i - iAtStart < chunkSize && - i < CORPUS_SIZE); - if (i == CORPUS_SIZE) - db.put(docs, cb); - else - progress("make-documents", i / CORPUS_SIZE, makeNextDocument); - } - - makeNextDocument(); -} - -var config = document.getElementById("config"); -var statusArea = document.getElementById("status"); -var result = document.getElementById("result"); - -statusArea.textContent = "Please wait..."; -config.textContent = ("Counting word occurrences in a lexicon of " + - LEXICON_SIZE + " words, using a corpus of " + - CORPUS_SIZE + " documents, each of which is " + - MIN_DOCUMENT_LENGTH + " to " + MAX_DOCUMENT_LENGTH + - " words long."); - -function makeProgress(func) { - var lastDate = new Date(); - function progress(phase, percent, resume) { - var currDate = new Date(); - if (currDate - lastDate > UI_LOCK_LIMIT) { - lastDate = currDate; - func.call(this, phase, percent); - window.setTimeout(resume, UI_BREATHE_TIME); - } else - window.setTimeout(resume, 0); - } - - return progress; -} - -function start() { - BrowserCouch.get( - "big", - function(db) { - if (db.getLength() == 0) { - db.wipe(function() { - makeCorpus( - db, - makeProgress( - function(phase, percent) { - statusArea.textContent = ("building new corpus (" + - Math.floor(percent * 100) + - "%)"); - }), - 25, - run - ); - }); - } else - run(); - - function run() { - db.view( - {map: function(doc, emit) { - var words = doc.content.split(" "); - for (var i = 0; i < words.length; i++) - emit(words[i], 1); - }, - reduce: function(keys, values) { - var sum = 0; - for (var i = 0; i < values.length; i++) - sum += values[i]; - return sum; - }, - chunkSize: 25, - progress: makeProgress( - function(phase, percent) { - percent = Math.floor(percent * 100); - var msg = phase + " (" + percent + "%)"; - statusArea.textContent = msg; - }), - finished: function(aResult) { - statusArea.textContent = "Done."; - - ModuleLoader.require( - "JSON", - function() { - result.textContent = JSON.stringify(aResult); - }); - }} - ); - } - }, - new FakeStorage() - ); -} - -window.addEventListener("load", start, false);
--- a/browser-couch.js Wed Apr 15 08:01:14 2009 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,568 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (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.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Ubiquity. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2007 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Atul Varma <atul@mozilla.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -function isArray(value) { - // Taken from "Remedial Javascript" by Douglas Crockford: - // http://javascript.crockford.com/remedial.html - - return (typeof value.length === 'number' && - !(value.propertyIsEnumerable('length')) && - typeof value.splice === 'function'); -} - -var ModuleLoader = { - LIBS: {JSON: "json2.js"}, - - require: function ML_require(libs, cb) { - var self = this; - var i = 0; - var lastLib = ""; - - if (!isArray(libs)) - libs = [libs]; - - function loadNextLib() { - if (lastLib && !window[lastLib]) - throw new Error("Failed to load library: " + lastLib); - if (i == libs.length) - cb(); - else { - var libName = libs[i]; - i += 1; - if (window[libName]) - loadNextLib(); - else { - var libUrl = self.LIBS[libName]; - if (!libUrl) - throw new Error("Unknown lib: " + libName); - lastLib = libName; - self._loadScript(libUrl, window, loadNextLib); - } - } - } - - loadNextLib(); - }, - - _loadScript: function ML__loadScript(url, window, cb) { - var doc = window.document; - var script = doc.createElement("script"); - script.setAttribute("src", url); - script.addEventListener( - "load", - function onLoad() { - script.removeEventListener("load", onLoad, false); - cb(); - }, - false - ); - doc.body.appendChild(script); - } -}; - -function WebWorkerMapReducer(numWorkers, Worker) { - if (!Worker) - Worker = window.Worker; - - var pool = []; - - function MapWorker(id) { - var worker = new Worker('worker-map-reducer.js'); - var onDone; - - worker.onmessage = function(event) { - onDone(event.data); - }; - - this.id = id; - this.map = function MW_map(map, dict, cb) { - onDone = cb; - worker.postMessage({map: map.toString(), dict: dict}); - }; - } - - for (var i = 0; i < numWorkers; i++) - pool.push(new MapWorker(i)); - - this.map = function WWMR_map(map, dict, progress, chunkSize, finished) { - var keys = dict.getKeys(); - var size = keys.length; - var workersDone = 0; - var mapDict = {}; - - function getNextChunk() { - if (keys.length) { - var chunkKeys = keys.slice(0, chunkSize); - keys = keys.slice(chunkSize); - var chunk = {}; - for (var i = 0; i < chunkKeys.length; i++) - chunk[chunkKeys[i]] = dict.get(chunkKeys[i]); - return chunk; - } else - return null; - } - - function nextJob(mapWorker) { - var chunk = getNextChunk(); - if (chunk) { - mapWorker.map( - map, - chunk, - function jobDone(aMapDict) { - for (name in aMapDict) - if (name in mapDict) { - var item = mapDict[name]; - item.keys = item.keys.concat(aMapDict[name].keys); - item.values = item.values.concat(aMapDict[name].values); - } else - mapDict[name] = aMapDict[name]; - - if (keys.length) - progress("map", - (size - keys.length) / size, - function() { nextJob(mapWorker); }); - else - workerDone(); - }); - } else - workerDone(); - } - - function workerDone() { - workersDone += 1; - if (workersDone == numWorkers) - allWorkersDone(); - } - - function allWorkersDone() { - var mapKeys = []; - for (name in mapDict) - mapKeys.push(name); - mapKeys.sort(); - finished({dict: mapDict, keys: mapKeys}); - } - - for (var i = 0; i < numWorkers; i++) - nextJob(pool[i]); - }; - - this.reduce = SingleThreadedMapReducer.reduce; -}; - -var SingleThreadedMapReducer = { - map: function STMR_map(map, dict, progress, - chunkSize, finished) { - var mapDict = {}; - var keys = dict.getKeys(); - var currDoc; - - function emit(key, value) { - // TODO: This assumes that the key will always be - // an indexable value. We may have to hash the value, - // though, if it's e.g. an Object. - var item = mapDict[key]; - if (!item) - item = mapDict[key] = {keys: [], values: []}; - item.keys.push(currDoc.id); - item.values.push(value); - } - - var i = 0; - - function continueMap() { - var iAtStart = i; - - do { - currDoc = dict.get(keys[i]); - map(currDoc, emit); - i++; - } while (i - iAtStart < chunkSize && - i < keys.length) - - if (i == keys.length) { - var mapKeys = []; - for (name in mapDict) - mapKeys.push(name); - mapKeys.sort(); - finished({dict: mapDict, keys: mapKeys}); - } else - progress("map", i / keys.length, continueMap); - } - - continueMap(); - }, - - reduce: function STMR_reduce(reduce, mapResult, progress, - chunkSize, finished) { - var rows = []; - var mapDict = mapResult.dict; - var mapKeys = mapResult.keys; - - var i = 0; - - function continueReduce() { - var iAtStart = i; - - do { - var key = mapKeys[i]; - var item = mapDict[key]; - - var keys = []; - for (var j = 0; j < keys.length; j++) - newKeys.push([key, item.keys[j]]); - - rows.push({key: key, - value: reduce(keys, item.values)}); - i++; - } while (i - iAtStart < chunkSize && - i < mapKeys.length) - - if (i == mapKeys.length) - finished(rows); - else - progress("reduce", i / mapKeys.length, continueReduce); - } - - continueReduce(); - } -}; - -function FakeStorage() { - var db = {}; - - function deepCopy(obj) { - if (typeof(obj) == "object") { - var copy; - - if (isArray(obj)) - copy = new Array(); - else - copy = new Object(); - - for (name in obj) { - if (obj.hasOwnProperty(name)) { - var property = obj[name]; - if (typeof(property) == "object") - copy[name] = deepCopy(property); - else - copy[name] = property; - } - } - - return copy; - } else - return obj; - } - - this.get = function FS_get(name, cb) { - if (!(name in db)) - cb(null); - else - cb(db[name]); - }; - - this.put = function FS_put(name, obj, cb) { - db[name] = deepCopy(obj); - cb(); - }; -}; - -function LocalStorage(JSON) { - var storage; - - if (window.globalStorage) - storage = window.globalStorage[location.hostname]; - else { - if (window.localStorage) - storage = window.localStorage; - else - throw new Error("globalStorage/localStorage not available."); - } - - function ensureJSON(cb) { - if (!JSON) { - ModuleLoader.require( - "JSON", - function() { - JSON = window.JSON; - cb(); - }); - } else - cb(); - } - - this.get = function LS_get(name, cb) { - if (name in storage && storage[name].value) - ensureJSON( - function() { - var obj = JSON.parse(storage[name].value); - cb(obj); - }); - else - cb(null); - }; - - this.put = function LS_put(name, obj, cb) { - ensureJSON( - function() { - storage[name] = JSON.stringify(obj); - cb(); - }); - }; -} - -var BrowserCouch = { - get: function BC_get(name, cb, storage) { - if (!storage) - storage = new LocalStorage(); - - new this._DB(name, storage, new this._Dictionary(), cb); - }, - - _Dictionary: function BC__Dictionary() { - var dict = {}; - var keys = []; - - function regenerateKeys() { - keys = []; - for (key in dict) - keys.push(key); - } - - this.has = function Dictionary_has(key) { - return (key in dict); - }; - - this.getKeys = function Dictionary_getKeys() { - return keys; - }; - - this.get = function Dictionary_get(key) { - return dict[key]; - }; - - this.set = function Dictionary_set(key, value) { - if (!(key in dict)) - keys.push(key); - dict[key] = value; - }; - - this.remove = function Dictionary_delete(key) { - delete dict[key]; - - // TODO: If we're in JS 1.6 and have Array.indexOf(), we - // shouldn't have to rebuild the key index like this. - regenerateKeys(); - }; - - this.clear = function Dictionary_clear() { - dict = {}; - keys = []; - }; - - this.pickle = function Dictionary_pickle() { - return dict; - }; - - this.unpickle = function Dictionary_unpickle(obj) { - dict = obj; - regenerateKeys(); - }; - }, - - _DB: function BC__DB(name, storage, dict, cb) { - var self = this; - var dbName = 'BrowserCouch_DB_' + name; - - function commitToStorage(cb) { - if (!cb) - cb = function() {}; - storage.put(dbName, dict.pickle(), cb); - } - - this.wipe = function DB_wipe(cb) { - dict.clear(); - commitToStorage(cb); - }; - - this.get = function DB_get(id, cb) { - if (dict.has(id)) - cb(dict.get(id)); - else - cb(null); - }; - - this.put = function DB_put(document, cb) { - if (isArray(document)) { - for (var i = 0; i < document.length; i++) - dict.set(document[i].id, document[i]); - } else - dict.set(document.id, document); - - commitToStorage(cb); - }; - - this.getLength = function DB_getLength() { - return dict.getKeys().length; - }; - - this.view = function DB_view(options) { - if (!options.map) - throw new Error('map function not provided'); - if (!options.finished) - throw new Error('finished callback not provided'); - - // Maximum number of items to process before giving the UI a chance - // to breathe. - var DEFAULT_CHUNK_SIZE = 1000; - - // If no progress callback is given, we'll automatically give the - // UI a chance to breathe for this many milliseconds before continuing - // processing. - var DEFAULT_UI_BREATHE_TIME = 50; - - var chunkSize = options.chunkSize; - if (!chunkSize) - chunkSize = DEFAULT_CHUNK_SIZE; - - var progress = options.progress; - if (!progress) - progress = function defaultProgress(phase, percent, resume) { - window.setTimeout(resume, DEFAULT_UI_BREATHE_TIME); - }; - - var mapReducer = options.mapReducer; - if (!mapReducer) - mapReducer = SingleThreadedMapReducer; - - mapReducer.map( - options.map, - dict, - progress, - chunkSize, - function(mapResult) { - if (options.reduce) - mapReducer.reduce( - options.reduce, - mapResult, - progress, - chunkSize, - function(rows) { - options.finished(new BrowserCouch._View(rows)); - }); - else - options.finished(new BrowserCouch._MapView(mapResult)); - }); - }; - - storage.get( - dbName, - function(obj) { - if (obj) - dict.unpickle(obj); - cb(self); - }); - }, - - _View: function BC__View(rows) { - this.rows = rows; - - function findRow(key, rows) { - if (rows.length > 1) { - var midpoint = Math.floor(rows.length / 2); - var row = rows[midpoint]; - if (key < row.key) - return findRow(key, rows.slice(0, midpoint)); - if (key > row.key) - return midpoint + findRow(key, rows.slice(midpoint)); - return midpoint; - } else - return 0; - } - - this.findRow = function V_findRow(key) { - return findRow(key, rows); - }; - }, - - _MapView: function BC__MapView(mapResult) { - var rows = []; - var keyRows = []; - - var mapKeys = mapResult.keys; - var mapDict = mapResult.dict; - - for (var i = 0; i < mapKeys.length; i++) { - var key = mapKeys[i]; - var item = mapDict[key]; - keyRows.push({key: key, pos: rows.length}); - var newRows = []; - for (var j = 0; j < item.keys.length; j++) { - var id = item.keys[j]; - var value = item.values[j]; - newRows.push({id: id, - key: key, - value: value}); - } - newRows.sort(function(a, b) { - if (a.id < b.id) - return -1; - if (a.id > b.id) - return 1; - return 0; - }); - rows = rows.concat(newRows); - } - - function findRow(key, keyRows) { - if (keyRows.length > 1) { - var midpoint = Math.floor(keyRows.length / 2); - var keyRow = keyRows[midpoint]; - if (key < keyRow.key) - return findRow(key, keyRows.slice(0, midpoint)); - if (key > keyRow.key) - return findRow(key, keyRows.slice(midpoint)); - return keyRow.pos; - } else - return keyRows[0].pos; - } - - this.rows = rows; - this.findRow = function MV_findRow(key) { - return findRow(key, keyRows); - }; - } -};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/css/index.css Wed Apr 15 08:07:42 2009 -0700 @@ -0,0 +1,31 @@ +body { + width: 20em; + font-family: helvetica; +} + +h1 { + font-weight: normal; +} + +.test { + font-family: monospace; +} + +.in-progress { + background-color: yellow; +} + +.successful { + background-color: lawnGreen; +} + +.failed { + background-color: red; +} + +.pending { +} + +.skipped { + background-color: lightGrey; +}
--- a/index.css Wed Apr 15 08:01:14 2009 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -body { - width: 20em; - font-family: helvetica; -} - -h1 { - font-weight: normal; -} - -.test { - font-family: monospace; -} - -.in-progress { - background-color: yellow; -} - -.successful { - background-color: lawnGreen; -} - -.failed { - background-color: red; -} - -.pending { -} - -.skipped { - background-color: lightGrey; -}
--- a/index.html Wed Apr 15 08:01:14 2009 -0700 +++ b/index.html Wed Apr 15 08:07:42 2009 -0700 @@ -6,7 +6,7 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <link rel="stylesheet" type="text/css" media="all" - href="index.css" /> + href="css/index.css" /> <title>Browser Couch Tests</title> </head> <body> @@ -14,8 +14,8 @@ <div id="status"> </div> </body> -<script src="browser-couch.js"></script> -<script src="tests.js"></script> +<script src="js/browser-couch.js"></script> +<script src="js/tests.js"></script> <script> var listener = { onReady: function(tests) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/big.js Wed Apr 15 08:07:42 2009 -0700 @@ -0,0 +1,156 @@ +var MAX_WORD_LENGTH = 10; +var LEXICON_SIZE = 200; +var MIN_DOCUMENT_LENGTH = 250; +var MAX_DOCUMENT_LENGTH = 500; +var CORPUS_SIZE = 1000; +var UI_LOCK_LIMIT = 100; +var UI_BREATHE_TIME = 10; + +// Returns a random integer between min and max +// Using Math.round() will give you a non-uniform distribution! +function getRandomInt(min, max) +{ + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function makeRandomWord() { + var word = ""; + var len = getRandomInt(1, MAX_WORD_LENGTH); + for (var i = 0; i < len; i++) { + var charCode = getRandomInt("a".charCodeAt(0), + "z".charCodeAt(0)); + var letter = String.fromCharCode(charCode); + word += letter; + } + return word; +} + +function makeLexicon() { + var lexicon = []; + + for (var i = 0; i < LEXICON_SIZE; i++) + lexicon.push(makeRandomWord()); + + return lexicon; +} + +function makeDocument(lexicon) { + var len = getRandomInt(MIN_DOCUMENT_LENGTH, + MAX_DOCUMENT_LENGTH); + var doc = []; + + for (var i = 0; i < len; i++) { + var wordIndex = getRandomInt(0, lexicon.length); + doc.push(lexicon[wordIndex]); + } + + return doc.join(" "); +} + +function makeCorpus(db, progress, chunkSize, cb) { + var lexicon = makeLexicon(); + var docs = []; + var i = 0; + + function makeNextDocument() { + var iAtStart = i; + + do { + docs.push({id: i, + content: makeDocument(lexicon)}); + i += 1; + } while (i - iAtStart < chunkSize && + i < CORPUS_SIZE); + if (i == CORPUS_SIZE) + db.put(docs, cb); + else + progress("make-documents", i / CORPUS_SIZE, makeNextDocument); + } + + makeNextDocument(); +} + +var config = document.getElementById("config"); +var statusArea = document.getElementById("status"); +var result = document.getElementById("result"); + +statusArea.textContent = "Please wait..."; +config.textContent = ("Counting word occurrences in a lexicon of " + + LEXICON_SIZE + " words, using a corpus of " + + CORPUS_SIZE + " documents, each of which is " + + MIN_DOCUMENT_LENGTH + " to " + MAX_DOCUMENT_LENGTH + + " words long."); + +function makeProgress(func) { + var lastDate = new Date(); + function progress(phase, percent, resume) { + var currDate = new Date(); + if (currDate - lastDate > UI_LOCK_LIMIT) { + lastDate = currDate; + func.call(this, phase, percent); + window.setTimeout(resume, UI_BREATHE_TIME); + } else + window.setTimeout(resume, 0); + } + + return progress; +} + +function start() { + BrowserCouch.get( + "big", + function(db) { + if (db.getLength() == 0) { + db.wipe(function() { + makeCorpus( + db, + makeProgress( + function(phase, percent) { + statusArea.textContent = ("building new corpus (" + + Math.floor(percent * 100) + + "%)"); + }), + 25, + run + ); + }); + } else + run(); + + function run() { + db.view( + {map: function(doc, emit) { + var words = doc.content.split(" "); + for (var i = 0; i < words.length; i++) + emit(words[i], 1); + }, + reduce: function(keys, values) { + var sum = 0; + for (var i = 0; i < values.length; i++) + sum += values[i]; + return sum; + }, + chunkSize: 25, + progress: makeProgress( + function(phase, percent) { + percent = Math.floor(percent * 100); + var msg = phase + " (" + percent + "%)"; + statusArea.textContent = msg; + }), + finished: function(aResult) { + statusArea.textContent = "Done."; + + ModuleLoader.require( + "JSON", + function() { + result.textContent = JSON.stringify(aResult); + }); + }} + ); + } + }, + new FakeStorage() + ); +} + +window.addEventListener("load", start, false);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/browser-couch.js Wed Apr 15 08:07:42 2009 -0700 @@ -0,0 +1,568 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ubiquity. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Atul Varma <atul@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +function isArray(value) { + // Taken from "Remedial Javascript" by Douglas Crockford: + // http://javascript.crockford.com/remedial.html + + return (typeof value.length === 'number' && + !(value.propertyIsEnumerable('length')) && + typeof value.splice === 'function'); +} + +var ModuleLoader = { + LIBS: {JSON: "js/ext/json2.js"}, + + require: function ML_require(libs, cb) { + var self = this; + var i = 0; + var lastLib = ""; + + if (!isArray(libs)) + libs = [libs]; + + function loadNextLib() { + if (lastLib && !window[lastLib]) + throw new Error("Failed to load library: " + lastLib); + if (i == libs.length) + cb(); + else { + var libName = libs[i]; + i += 1; + if (window[libName]) + loadNextLib(); + else { + var libUrl = self.LIBS[libName]; + if (!libUrl) + throw new Error("Unknown lib: " + libName); + lastLib = libName; + self._loadScript(libUrl, window, loadNextLib); + } + } + } + + loadNextLib(); + }, + + _loadScript: function ML__loadScript(url, window, cb) { + var doc = window.document; + var script = doc.createElement("script"); + script.setAttribute("src", url); + script.addEventListener( + "load", + function onLoad() { + script.removeEventListener("load", onLoad, false); + cb(); + }, + false + ); + doc.body.appendChild(script); + } +}; + +function WebWorkerMapReducer(numWorkers, Worker) { + if (!Worker) + Worker = window.Worker; + + var pool = []; + + function MapWorker(id) { + var worker = new Worker('js/worker-map-reducer.js'); + var onDone; + + worker.onmessage = function(event) { + onDone(event.data); + }; + + this.id = id; + this.map = function MW_map(map, dict, cb) { + onDone = cb; + worker.postMessage({map: map.toString(), dict: dict}); + }; + } + + for (var i = 0; i < numWorkers; i++) + pool.push(new MapWorker(i)); + + this.map = function WWMR_map(map, dict, progress, chunkSize, finished) { + var keys = dict.getKeys(); + var size = keys.length; + var workersDone = 0; + var mapDict = {}; + + function getNextChunk() { + if (keys.length) { + var chunkKeys = keys.slice(0, chunkSize); + keys = keys.slice(chunkSize); + var chunk = {}; + for (var i = 0; i < chunkKeys.length; i++) + chunk[chunkKeys[i]] = dict.get(chunkKeys[i]); + return chunk; + } else + return null; + } + + function nextJob(mapWorker) { + var chunk = getNextChunk(); + if (chunk) { + mapWorker.map( + map, + chunk, + function jobDone(aMapDict) { + for (name in aMapDict) + if (name in mapDict) { + var item = mapDict[name]; + item.keys = item.keys.concat(aMapDict[name].keys); + item.values = item.values.concat(aMapDict[name].values); + } else + mapDict[name] = aMapDict[name]; + + if (keys.length) + progress("map", + (size - keys.length) / size, + function() { nextJob(mapWorker); }); + else + workerDone(); + }); + } else + workerDone(); + } + + function workerDone() { + workersDone += 1; + if (workersDone == numWorkers) + allWorkersDone(); + } + + function allWorkersDone() { + var mapKeys = []; + for (name in mapDict) + mapKeys.push(name); + mapKeys.sort(); + finished({dict: mapDict, keys: mapKeys}); + } + + for (var i = 0; i < numWorkers; i++) + nextJob(pool[i]); + }; + + this.reduce = SingleThreadedMapReducer.reduce; +}; + +var SingleThreadedMapReducer = { + map: function STMR_map(map, dict, progress, + chunkSize, finished) { + var mapDict = {}; + var keys = dict.getKeys(); + var currDoc; + + function emit(key, value) { + // TODO: This assumes that the key will always be + // an indexable value. We may have to hash the value, + // though, if it's e.g. an Object. + var item = mapDict[key]; + if (!item) + item = mapDict[key] = {keys: [], values: []}; + item.keys.push(currDoc.id); + item.values.push(value); + } + + var i = 0; + + function continueMap() { + var iAtStart = i; + + do { + currDoc = dict.get(keys[i]); + map(currDoc, emit); + i++; + } while (i - iAtStart < chunkSize && + i < keys.length) + + if (i == keys.length) { + var mapKeys = []; + for (name in mapDict) + mapKeys.push(name); + mapKeys.sort(); + finished({dict: mapDict, keys: mapKeys}); + } else + progress("map", i / keys.length, continueMap); + } + + continueMap(); + }, + + reduce: function STMR_reduce(reduce, mapResult, progress, + chunkSize, finished) { + var rows = []; + var mapDict = mapResult.dict; + var mapKeys = mapResult.keys; + + var i = 0; + + function continueReduce() { + var iAtStart = i; + + do { + var key = mapKeys[i]; + var item = mapDict[key]; + + var keys = []; + for (var j = 0; j < keys.length; j++) + newKeys.push([key, item.keys[j]]); + + rows.push({key: key, + value: reduce(keys, item.values)}); + i++; + } while (i - iAtStart < chunkSize && + i < mapKeys.length) + + if (i == mapKeys.length) + finished(rows); + else + progress("reduce", i / mapKeys.length, continueReduce); + } + + continueReduce(); + } +}; + +function FakeStorage() { + var db = {}; + + function deepCopy(obj) { + if (typeof(obj) == "object") { + var copy; + + if (isArray(obj)) + copy = new Array(); + else + copy = new Object(); + + for (name in obj) { + if (obj.hasOwnProperty(name)) { + var property = obj[name]; + if (typeof(property) == "object") + copy[name] = deepCopy(property); + else + copy[name] = property; + } + } + + return copy; + } else + return obj; + } + + this.get = function FS_get(name, cb) { + if (!(name in db)) + cb(null); + else + cb(db[name]); + }; + + this.put = function FS_put(name, obj, cb) { + db[name] = deepCopy(obj); + cb(); + }; +}; + +function LocalStorage(JSON) { + var storage; + + if (window.globalStorage) + storage = window.globalStorage[location.hostname]; + else { + if (window.localStorage) + storage = window.localStorage; + else + throw new Error("globalStorage/localStorage not available."); + } + + function ensureJSON(cb) { + if (!JSON) { + ModuleLoader.require( + "JSON", + function() { + JSON = window.JSON; + cb(); + }); + } else + cb(); + } + + this.get = function LS_get(name, cb) { + if (name in storage && storage[name].value) + ensureJSON( + function() { + var obj = JSON.parse(storage[name].value); + cb(obj); + }); + else + cb(null); + }; + + this.put = function LS_put(name, obj, cb) { + ensureJSON( + function() { + storage[name] = JSON.stringify(obj); + cb(); + }); + }; +} + +var BrowserCouch = { + get: function BC_get(name, cb, storage) { + if (!storage) + storage = new LocalStorage(); + + new this._DB(name, storage, new this._Dictionary(), cb); + }, + + _Dictionary: function BC__Dictionary() { + var dict = {}; + var keys = []; + + function regenerateKeys() { + keys = []; + for (key in dict) + keys.push(key); + } + + this.has = function Dictionary_has(key) { + return (key in dict); + }; + + this.getKeys = function Dictionary_getKeys() { + return keys; + }; + + this.get = function Dictionary_get(key) { + return dict[key]; + }; + + this.set = function Dictionary_set(key, value) { + if (!(key in dict)) + keys.push(key); + dict[key] = value; + }; + + this.remove = function Dictionary_delete(key) { + delete dict[key]; + + // TODO: If we're in JS 1.6 and have Array.indexOf(), we + // shouldn't have to rebuild the key index like this. + regenerateKeys(); + }; + + this.clear = function Dictionary_clear() { + dict = {}; + keys = []; + }; + + this.pickle = function Dictionary_pickle() { + return dict; + }; + + this.unpickle = function Dictionary_unpickle(obj) { + dict = obj; + regenerateKeys(); + }; + }, + + _DB: function BC__DB(name, storage, dict, cb) { + var self = this; + var dbName = 'BrowserCouch_DB_' + name; + + function commitToStorage(cb) { + if (!cb) + cb = function() {}; + storage.put(dbName, dict.pickle(), cb); + } + + this.wipe = function DB_wipe(cb) { + dict.clear(); + commitToStorage(cb); + }; + + this.get = function DB_get(id, cb) { + if (dict.has(id)) + cb(dict.get(id)); + else + cb(null); + }; + + this.put = function DB_put(document, cb) { + if (isArray(document)) { + for (var i = 0; i < document.length; i++) + dict.set(document[i].id, document[i]); + } else + dict.set(document.id, document); + + commitToStorage(cb); + }; + + this.getLength = function DB_getLength() { + return dict.getKeys().length; + }; + + this.view = function DB_view(options) { + if (!options.map) + throw new Error('map function not provided'); + if (!options.finished) + throw new Error('finished callback not provided'); + + // Maximum number of items to process before giving the UI a chance + // to breathe. + var DEFAULT_CHUNK_SIZE = 1000; + + // If no progress callback is given, we'll automatically give the + // UI a chance to breathe for this many milliseconds before continuing + // processing. + var DEFAULT_UI_BREATHE_TIME = 50; + + var chunkSize = options.chunkSize; + if (!chunkSize) + chunkSize = DEFAULT_CHUNK_SIZE; + + var progress = options.progress; + if (!progress) + progress = function defaultProgress(phase, percent, resume) { + window.setTimeout(resume, DEFAULT_UI_BREATHE_TIME); + }; + + var mapReducer = options.mapReducer; + if (!mapReducer) + mapReducer = SingleThreadedMapReducer; + + mapReducer.map( + options.map, + dict, + progress, + chunkSize, + function(mapResult) { + if (options.reduce) + mapReducer.reduce( + options.reduce, + mapResult, + progress, + chunkSize, + function(rows) { + options.finished(new BrowserCouch._View(rows)); + }); + else + options.finished(new BrowserCouch._MapView(mapResult)); + }); + }; + + storage.get( + dbName, + function(obj) { + if (obj) + dict.unpickle(obj); + cb(self); + }); + }, + + _View: function BC__View(rows) { + this.rows = rows; + + function findRow(key, rows) { + if (rows.length > 1) { + var midpoint = Math.floor(rows.length / 2); + var row = rows[midpoint]; + if (key < row.key) + return findRow(key, rows.slice(0, midpoint)); + if (key > row.key) + return midpoint + findRow(key, rows.slice(midpoint)); + return midpoint; + } else + return 0; + } + + this.findRow = function V_findRow(key) { + return findRow(key, rows); + }; + }, + + _MapView: function BC__MapView(mapResult) { + var rows = []; + var keyRows = []; + + var mapKeys = mapResult.keys; + var mapDict = mapResult.dict; + + for (var i = 0; i < mapKeys.length; i++) { + var key = mapKeys[i]; + var item = mapDict[key]; + keyRows.push({key: key, pos: rows.length}); + var newRows = []; + for (var j = 0; j < item.keys.length; j++) { + var id = item.keys[j]; + var value = item.values[j]; + newRows.push({id: id, + key: key, + value: value}); + } + newRows.sort(function(a, b) { + if (a.id < b.id) + return -1; + if (a.id > b.id) + return 1; + return 0; + }); + rows = rows.concat(newRows); + } + + function findRow(key, keyRows) { + if (keyRows.length > 1) { + var midpoint = Math.floor(keyRows.length / 2); + var keyRow = keyRows[midpoint]; + if (key < keyRow.key) + return findRow(key, keyRows.slice(0, midpoint)); + if (key > keyRow.key) + return findRow(key, keyRows.slice(midpoint)); + return keyRow.pos; + } else + return keyRows[0].pos; + } + + this.rows = rows; + this.findRow = function MV_findRow(key) { + return findRow(key, keyRows); + }; + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/ext/json2.js Wed Apr 15 08:07:42 2009 -0700 @@ -0,0 +1,478 @@ +/* + http://www.JSON.org/json2.js + 2008-11-19 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + 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 object holding the key. + + 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. + + 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. +*/ + +/*jslint evil: true */ + +/*global JSON */ + +/*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. + +if (!this.JSON) { + JSON = {}; +} +(function () { + + 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 this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + 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. + + 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'); + }; + } +})();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/tests.js Wed Apr 15 08:07:42 2009 -0700 @@ -0,0 +1,217 @@ +var Tests = { + run: function(listener, container, setTimeout) { + if (!container) + container = this; + if (!setTimeout) + setTimeout = window.setTimeout; + + var tests = []; + + for (name in container) + if (name.indexOf("test") == "0") { + var test = { + name: name, + func: container[name], + isAsync: name.indexOf("_async") != -1, + id: tests.length, + assertEqual: function assertEqual(a, b) { + if (a != b) + throw new Error(a + " != " + b); + } + }; + tests.push(test); + } + + listener.onReady(tests); + var nextTest = 0; + + function runNextTest() { + if (nextTest < tests.length) { + var test = tests[nextTest]; + listener.onRun(test); + test.skip = function() { + listener.onSkip(this); + setTimeout(runNextTest, 0); + }; + test.done = function() { + listener.onFinish(this); + setTimeout(runNextTest, 0); + }; + test.func.call(container, test); + if (!test.isAsync) + test.done(); + nextTest++; + } + } + + runNextTest(); + }, + testDictionary: function(self) { + var dict = new BrowserCouch._Dictionary(); + dict.set('foo', {a: 'hello'}); + dict.set('bar', {b: 'goodbye'}); + self.assertEqual(dict.get('foo').a, 'hello'); + self.assertEqual(dict.get('bar').b, 'goodbye'); + self.assertEqual(dict.getKeys().length, 2); + self.assertEqual(dict.has('foo'), true); + self.assertEqual(dict.has('bar'), true); + self.assertEqual(dict.has('spatula'), false); + dict.remove('bar'); + self.assertEqual(dict.getKeys().length, 1); + self.assertEqual(dict.has('foo'), true); + }, + _setupTestDb: function(cb) { + BrowserCouch.get( + "blarg", + function(db) { + db.wipe( + function() { + db.put( + [{id: "monkey", + content: "hello there dude"}, + {id: "chunky", + content: "hello there dogen"}], + function() { + ModuleLoader.require( + "JSON", + function() { cb(db); } + ); + } + ); + }); + }, + new FakeStorage() + ); + }, + _mapWordFrequencies: function(doc, emit) { + var words = doc.content.split(" "); + for (var i = 0; i < words.length; i++) + emit(words[i], 1); + }, + _reduceWordFrequencies: function(keys, values) { + var sum = 0; + for (var i = 0; i < values.length; i++) + sum += values[i]; + return sum; + }, + testViewMap_async: function(self) { + var map = this._mapWordFrequencies; + this._setupTestDb( + function(db) { + db.view( + {map: map, + finished: function(result) { + var expected = { + rows:[{"id":"chunky","key":"dogen","value":1}, + {"id":"monkey","key":"dude","value":1}, + {"id":"chunky","key":"hello","value":1}, + {"id":"monkey","key":"hello","value":1}, + {"id":"chunky","key":"there","value":1}, + {"id":"monkey","key":"there","value":1}] + }; + self.assertEqual(JSON.stringify(expected), + JSON.stringify(result)); + self.done(); + }}); + }); + }, + testViewMapFindRow_async: function(self) { + var map = this._mapWordFrequencies; + this._setupTestDb( + function(db) { + db.view( + {map: map, + finished: function(view) { + self.assertEqual(view.findRow("dogen"), 0); + self.assertEqual(view.findRow("dude"), 1); + self.assertEqual(view.findRow("hello"), 2); + self.assertEqual(view.findRow("there"), 4); + self.done(); + }}); + }); + }, + testViewProgress_async: function(self) { + var map = this._mapWordFrequencies; + var reduce = this._reduceWordFrequencies; + this._setupTestDb( + function(db) { + var progressCalled = false; + var timesProgressCalled = 0; + db.view( + {map: map, + reduce: reduce, + chunkSize: 1, + progress: function(phase, percentDone, resume) { + if (phase == "map") { + self.assertEqual(percentDone, 0.5); + progressCalled = true; + } + resume(); + }, + finished: function(result) { + self.assertEqual(progressCalled, true); + self.done(); + }}); + }); + }, + testViewMapReduceFindRow_async: function(self) { + var map = this._mapWordFrequencies; + var reduce = this._reduceWordFrequencies; + this._setupTestDb( + function(db) { + db.view( + {map: map, + reduce: reduce, + finished: function(view) { + self.assertEqual(view.findRow("dogen"), 0); + self.assertEqual(view.findRow("dude"), 1); + self.assertEqual(view.findRow("hello"), 2); + self.assertEqual(view.findRow("there"), 3); + self.done(); + }}); + }); + }, + testViewMapReduceWebWorker_async: function(self) { + if (window.Worker) { + var map = this._mapWordFrequencies; + var reduce = this._reduceWordFrequencies; + this._setupTestDb( + function(db) { + db.view( + {map: map, + reduce: reduce, + mapReducer: new WebWorkerMapReducer(2), + chunkSize: 1, + finished: function(result) { + var expected = {rows: [{key: "dogen", value: 1}, + {key: "dude", value: 1}, + {key: "hello", value: 2}, + {key: "there", value: 2}]}; + self.assertEqual(JSON.stringify(expected), + JSON.stringify(result)); + self.done(); + }}); + }); + } else + self.skip(); + }, + testViewMapReduce_async: function(self) { + var map = this._mapWordFrequencies; + var reduce = this._reduceWordFrequencies; + this._setupTestDb( + function(db) { + db.view( + {map: map, + reduce: reduce, + finished: function(result) { + var expected = {rows: [{key: "dogen", value: 1}, + {key: "dude", value: 1}, + {key: "hello", value: 2}, + {key: "there", value: 2}]}; + self.assertEqual(JSON.stringify(expected), + JSON.stringify(result)); + self.done(); + }}); + }); + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/worker-map-reducer.js Wed Apr 15 08:07:42 2009 -0700 @@ -0,0 +1,24 @@ +function map(func, dict) { + var mapDict = {}; + var currDoc; + + function emit(key, value) { + var item = mapDict[key]; + if (!item) + item = mapDict[key] = {keys: [], values: []}; + item.keys.push(currDoc.id); + item.values.push(value); + } + + for (key in dict) { + currDoc = dict[key]; + func(currDoc, emit); + } + + return mapDict; +} + +function onmessage(event) { + var mapFunc = eval("(" + event.data.map + ")"); + postMessage(map(mapFunc, event.data.dict)); +};
--- a/json2.js Wed Apr 15 08:01:14 2009 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,478 +0,0 @@ -/* - http://www.JSON.org/json2.js - 2008-11-19 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - 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 object holding the key. - - 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. - - 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. -*/ - -/*jslint evil: true */ - -/*global JSON */ - -/*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. - -if (!this.JSON) { - JSON = {}; -} -(function () { - - 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 this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - 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. - - 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'); - }; - } -})();
--- a/tests.js Wed Apr 15 08:01:14 2009 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,217 +0,0 @@ -var Tests = { - run: function(listener, container, setTimeout) { - if (!container) - container = this; - if (!setTimeout) - setTimeout = window.setTimeout; - - var tests = []; - - for (name in container) - if (name.indexOf("test") == "0") { - var test = { - name: name, - func: container[name], - isAsync: name.indexOf("_async") != -1, - id: tests.length, - assertEqual: function assertEqual(a, b) { - if (a != b) - throw new Error(a + " != " + b); - } - }; - tests.push(test); - } - - listener.onReady(tests); - var nextTest = 0; - - function runNextTest() { - if (nextTest < tests.length) { - var test = tests[nextTest]; - listener.onRun(test); - test.skip = function() { - listener.onSkip(this); - setTimeout(runNextTest, 0); - }; - test.done = function() { - listener.onFinish(this); - setTimeout(runNextTest, 0); - }; - test.func.call(container, test); - if (!test.isAsync) - test.done(); - nextTest++; - } - } - - runNextTest(); - }, - testDictionary: function(self) { - var dict = new BrowserCouch._Dictionary(); - dict.set('foo', {a: 'hello'}); - dict.set('bar', {b: 'goodbye'}); - self.assertEqual(dict.get('foo').a, 'hello'); - self.assertEqual(dict.get('bar').b, 'goodbye'); - self.assertEqual(dict.getKeys().length, 2); - self.assertEqual(dict.has('foo'), true); - self.assertEqual(dict.has('bar'), true); - self.assertEqual(dict.has('spatula'), false); - dict.remove('bar'); - self.assertEqual(dict.getKeys().length, 1); - self.assertEqual(dict.has('foo'), true); - }, - _setupTestDb: function(cb) { - BrowserCouch.get( - "blarg", - function(db) { - db.wipe( - function() { - db.put( - [{id: "monkey", - content: "hello there dude"}, - {id: "chunky", - content: "hello there dogen"}], - function() { - ModuleLoader.require( - "JSON", - function() { cb(db); } - ); - } - ); - }); - }, - new FakeStorage() - ); - }, - _mapWordFrequencies: function(doc, emit) { - var words = doc.content.split(" "); - for (var i = 0; i < words.length; i++) - emit(words[i], 1); - }, - _reduceWordFrequencies: function(keys, values) { - var sum = 0; - for (var i = 0; i < values.length; i++) - sum += values[i]; - return sum; - }, - testViewMap_async: function(self) { - var map = this._mapWordFrequencies; - this._setupTestDb( - function(db) { - db.view( - {map: map, - finished: function(result) { - var expected = { - rows:[{"id":"chunky","key":"dogen","value":1}, - {"id":"monkey","key":"dude","value":1}, - {"id":"chunky","key":"hello","value":1}, - {"id":"monkey","key":"hello","value":1}, - {"id":"chunky","key":"there","value":1}, - {"id":"monkey","key":"there","value":1}] - }; - self.assertEqual(JSON.stringify(expected), - JSON.stringify(result)); - self.done(); - }}); - }); - }, - testViewMapFindRow_async: function(self) { - var map = this._mapWordFrequencies; - this._setupTestDb( - function(db) { - db.view( - {map: map, - finished: function(view) { - self.assertEqual(view.findRow("dogen"), 0); - self.assertEqual(view.findRow("dude"), 1); - self.assertEqual(view.findRow("hello"), 2); - self.assertEqual(view.findRow("there"), 4); - self.done(); - }}); - }); - }, - testViewProgress_async: function(self) { - var map = this._mapWordFrequencies; - var reduce = this._reduceWordFrequencies; - this._setupTestDb( - function(db) { - var progressCalled = false; - var timesProgressCalled = 0; - db.view( - {map: map, - reduce: reduce, - chunkSize: 1, - progress: function(phase, percentDone, resume) { - if (phase == "map") { - self.assertEqual(percentDone, 0.5); - progressCalled = true; - } - resume(); - }, - finished: function(result) { - self.assertEqual(progressCalled, true); - self.done(); - }}); - }); - }, - testViewMapReduceFindRow_async: function(self) { - var map = this._mapWordFrequencies; - var reduce = this._reduceWordFrequencies; - this._setupTestDb( - function(db) { - db.view( - {map: map, - reduce: reduce, - finished: function(view) { - self.assertEqual(view.findRow("dogen"), 0); - self.assertEqual(view.findRow("dude"), 1); - self.assertEqual(view.findRow("hello"), 2); - self.assertEqual(view.findRow("there"), 3); - self.done(); - }}); - }); - }, - testViewMapReduceWebWorker_async: function(self) { - if (window.Worker) { - var map = this._mapWordFrequencies; - var reduce = this._reduceWordFrequencies; - this._setupTestDb( - function(db) { - db.view( - {map: map, - reduce: reduce, - mapReducer: new WebWorkerMapReducer(2), - chunkSize: 1, - finished: function(result) { - var expected = {rows: [{key: "dogen", value: 1}, - {key: "dude", value: 1}, - {key: "hello", value: 2}, - {key: "there", value: 2}]}; - self.assertEqual(JSON.stringify(expected), - JSON.stringify(result)); - self.done(); - }}); - }); - } else - self.skip(); - }, - testViewMapReduce_async: function(self) { - var map = this._mapWordFrequencies; - var reduce = this._reduceWordFrequencies; - this._setupTestDb( - function(db) { - db.view( - {map: map, - reduce: reduce, - finished: function(result) { - var expected = {rows: [{key: "dogen", value: 1}, - {key: "dude", value: 1}, - {key: "hello", value: 2}, - {key: "there", value: 2}]}; - self.assertEqual(JSON.stringify(expected), - JSON.stringify(result)); - self.done(); - }}); - }); - } -};
--- a/worker-map-reducer.js Wed Apr 15 08:01:14 2009 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -function map(func, dict) { - var mapDict = {}; - var currDoc; - - function emit(key, value) { - var item = mapDict[key]; - if (!item) - item = mapDict[key] = {keys: [], values: []}; - item.keys.push(currDoc.id); - item.values.push(value); - } - - for (key in dict) { - currDoc = dict[key]; - func(currDoc, emit); - } - - return mapDict; -} - -function onmessage(event) { - var mapFunc = eval("(" + event.data.map + ")"); - postMessage(map(mapFunc, event.data.dict)); -};