diff --git a/static/js/dict.js b/static/js/dict.js new file mode 100644 index 0000000000..7d9f844991 --- /dev/null +++ b/static/js/dict.js @@ -0,0 +1,77 @@ +/* Constructs a new Dict object. + * + * Dict() -> the new Dict will be empty + * Dict(otherdict) -> create a shallow copy of otherdict + * Dict(jsobj) -> create a Dict with keys corresponding to the properties of + * jsobj and values corresponding to the value of the appropriate + * property + */ +function Dict(obj) { + var self = this; + this._items = {}; + + if (typeof obj === "object" && obj !== null) { + if (obj instanceof Dict) { + _.each(obj.items(), function (kv) { + self.set(kv[0], kv[1]); + }); + } else { + _.each(obj, function (val, key) { + self.set(key, val); + }); + } + } +} + +(function () { + +function munge(k) { + return ':' + k; +} + +function unmunge(k) { + return k.substr(1); +} + +Dict.prototype = { + get: function Dict_get(key) { + return this._items[munge(key)]; + }, + + set: function Dict_set(key, value) { + return (this._items[munge(key)] = value); + }, + + has: function Dict_has(key) { + return _.has(this._items, munge(key)); + }, + + del: function Dict_del(key) { + return delete this._items[munge(key)]; + }, + + keys: function Dict_keys() { + return _.map(_.keys(this._items), unmunge); + }, + + values: function Dict_values() { + return _.values(this._items); + }, + + items: function Dict_items() { + return _.map(_.pairs(this._items), function (pair) { + return [unmunge(pair[0]), pair[1]]; + }); + } +}; + +Dict.prototype = _.object(_.map(Dict.prototype, + function (value, key) { + return [key, util.enforce_arity(value)]; + })); + +}()); + +if (typeof module !== 'undefined') { + module.exports = Dict; +} diff --git a/tools/jslint/check-all.js b/tools/jslint/check-all.js index f320982b87..92bc13a240 100644 --- a/tools/jslint/check-all.js +++ b/tools/jslint/check-all.js @@ -25,7 +25,7 @@ var globals = + ' composebox_typeahead typeahead_helper notifications hashchange' + ' invite ui util activity timerender MessageList blueslip unread stream_list' + ' onboarding message_edit tab_bar emoji popovers navigate message_tour' - + ' avatar feature_flags search_suggestion referral stream_color' + + ' avatar feature_flags search_suggestion referral stream_color Dict' // colorspace.js + ' colorspace' diff --git a/zerver/tests/frontend/node/dict.js b/zerver/tests/frontend/node/dict.js new file mode 100644 index 0000000000..a96d7ef14f --- /dev/null +++ b/zerver/tests/frontend/node/dict.js @@ -0,0 +1,66 @@ +global._ = require('third/underscore/underscore.js'); +global.util = require('js/util.js'); +var Dict = require('js/dict.js'); +var assert = require('assert'); + +(function test_basic() { + var d = new Dict(); + + assert.deepEqual(d.keys(), []); + + d.set('foo', 'bar'); + assert.equal(d.get('foo'), 'bar'); + + d.set('foo', 'baz'); + assert.equal(d.get('foo'), 'baz'); + + d.set('bar', 'qux'); + assert.equal(d.get('foo'), 'baz'); + assert.equal(d.get('bar'), 'qux'); + + assert.equal(d.has('bar'), true); + assert.equal(d.has('baz'), false); + + assert.deepEqual(d.keys(), ['foo', 'bar']); + assert.deepEqual(d.values(), ['baz', 'qux']); + assert.deepEqual(d.items(), [['foo', 'baz'], ['bar', 'qux']]); + + d.del('bar'); + assert.equal(d.has('bar'), false); + assert.strictEqual(d.get('bar'), undefined); + + assert.deepEqual(d.keys(), ['foo']); +}()); + +(function test_restricted_keys() { + var d = new Dict(); + + assert.equal(d.has('__proto__'), false); + assert.equal(d.has('hasOwnProperty'), false); + assert.equal(d.has('toString'), false); + + assert.strictEqual(d.get('__proto__'), undefined); + assert.strictEqual(d.get('hasOwnProperty'), undefined); + assert.strictEqual(d.get('toString'), undefined); + + d.set('hasOwnProperty', function () {return true;}); + assert.equal(d.has('blah'), false); + + d.set('__proto__', 'foo'); + d.set('foo', 'bar'); + assert.equal(d.get('foo'), 'bar'); +}()); + +(function test_construction() { + var d1 = new Dict(); + + assert.deepEqual(d1.items(), []); + + var d2 = new Dict({foo: 'bar', baz: 'qux'}); + assert.deepEqual(d2.items(), [['foo', 'bar'], ['baz', 'qux']]); + + var d3 = new Dict(d2); + d3.del('foo'); + assert.deepEqual(d2.items(), [['foo', 'bar'], ['baz', 'qux']]); + assert.deepEqual(d3.items(), [['baz', 'qux']]); +}()); diff --git a/zproject/settings.py b/zproject/settings.py index 8399310b4f..ae22fa0e54 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -363,6 +363,7 @@ JS_SPECS = { 'js/feature_flags.js', 'js/util.js', + 'js/dict.js', 'js/setup.js', 'js/viewport.js', 'js/rows.js',