Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rauschma committed Jan 3, 2012
0 parents commit cc2cb14
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
.idea

18 changes: 18 additions & 0 deletions README.md
@@ -0,0 +1,18 @@
A simple map from strings to values in JavaScript
=================================================

This implementation avoids the pitfalls of directly using objects for this purpose.

Usage on Node.js (on browsers, you need an AMD-compatible script loader such as [RequireJS](http://requirejs.org/)):

> var strmap = require("./strmap");
> var map = new strmap.StrMap({ foo: 1, bar: 2});
> map.get("foo")
1
> map.set("foo", "abc")

Tests
-----

- Run the tests via [Mocha](http://visionmedia.github.com/mocha/).
- Assertion API: [expect.js](https://github.com/LearnBoost/expect.js).
120 changes: 120 additions & 0 deletions strmap.js
@@ -0,0 +1,120 @@
"use strict";

({ define: typeof define === "function" ? define : function(A,F) { module.exports = F.apply(null, A.map(require)) } }).
define([],
function () {
var e = {};
var hasOwnProperty = Object.prototype.hasOwnProperty;

function getOwnPropertyValue(obj, propName) {
return hasOwnProperty.call(obj, propName) ? obj[propName] : undefined;
}

/*
* Make sure that there never is a key "__proto__".
* Exported for unit test.
*/
e._escapeKey = function (key) {
if (key.indexOf("__proto__") >= 0) {

This comment has been minimized.

Copy link
@Raynos

Raynos Jan 3, 2012

Contributor

Shouldn't this check be === 0 ? a key like "foo__proto__" shouldn't cause a problem.

This comment has been minimized.

Copy link
@rauschma

rauschma Jan 9, 2012

Author Owner

Good idea. Note: then you have to append the escape character. That is something that I hadn’t thought of previously – the above was my poor man’s endsWith(), but startsWith() is much easier to do via indexOf().

return "%"+key;
} else {
return key;
}
}

e.StrMap = function (obj) {
var that = this;

This comment has been minimized.

Copy link
@Raynos

Raynos Jan 3, 2012

Contributor

that is not used.

This comment has been minimized.

Copy link
@rauschma

rauschma Jan 9, 2012

Author Owner

Fixed.

this._data = {};
if (obj instanceof Object) {
copyOne(this._data, obj, true);
}
};

/**
* Add an entry to the map.
*
* @param key The key of the entry
* @param value The value of the entry
*/
e.StrMap.prototype.set = function (key, value) {
key = e._escapeKey(key);
this._data[key] = value;
};

/**
* Retrieve the value of an entry from the map.
*
* @param key The key of the entry
*/
e.StrMap.prototype.get = function (key) {
key = e._escapeKey(key);
return getOwnPropertyValue(this._data, key);
};
e.StrMap.prototype.delete = function (key) {
key = e._escapeKey(key);
var value = getOwnPropertyValue(this._data, key);
delete this._data[key];
return value;
};

/**
* Is there an entry with the given key in the map?
*/
e.StrMap.prototype.hasKey = function (key) {
key = e._escapeKey(key);
return hasOwnProperty.call(key);

This comment has been minimized.

Copy link
@Raynos

Raynos Jan 3, 2012

Contributor

hasOwnProperty.call(this._data, key)

This comment has been minimized.

Copy link
@rauschma

rauschma Jan 9, 2012

Author Owner

Good catch. Fixed.

};

/**
* Starting with the first argument, then the second, override
* the entries in this map. Each argument can be either
* a map or an object. Overriding means: copy each entry
* to this map. If there already is an entry that has the same
* key, remove it.
*/
e.StrMap.prototype.overrideWith = function () {
return this._copyProperties(arguments, true);
};

/**
* Similar to overrideWith(), but doesn’t override properties that
* already exist in this map.
*/
e.StrMap.prototype.supplementWith = function () {
return this._copyProperties(arguments, false);
};

/**
* Convert this map to an object.
*/
e.StrMap.prototype.toObject = function() {
return copyOne({}, this._data, true);
};

e.StrMap.prototype._copyProperties = function (args, override) {
var target = this._data;
for (var i=0; i<args.length; i++) {
var source = args[i];
if (source instanceof e.StrMap) {
source = source._data;
}
copyOne(target, source, override);
}
return this;
}

function copyOne(target, source, override) {
Object.keys(source).forEach(function(propName) {
if (override || !hasOwnProperty.call(target, propName)) {
target[propName] = source[propName];
}
});
return target;
}

return e;
}



);
37 changes: 37 additions & 0 deletions test/strmap-test.js
@@ -0,0 +1,37 @@
"use strict";

({ define: typeof define === "function" ? define : function(A,F) { module.exports = F.apply(null, A.map(require)) } }).
define([ "expect.js", "../strmap" ],
function (expect, strmap) {
describe('StrMap', function(){
it('should handle __proto__', function () {
var map = new strmap.StrMap();
map.set("__proto__", 123);
expect(map.get("__proto__")).to.equal(123);

// Escaping must avoid key clashes
var escapedProto = strmap._escapeKey("__proto__");
map.set(escapedProto, "abc");
expect(map.get(escapedProto)).to.equal("abc");

});
it('should not inherit keys', function () {
expect(new strmap.StrMap().hasKey("toString")).to.be.false;
});
it("imports and exports own properties of objects", function () {
var map = new strmap.StrMap({ foo: 1, bar: 2});
expect(map.toObject()).to.eql({ foo: 1, bar: 2 });
});
it("overrides", function () {
var map1 = new strmap.StrMap({ foo: 1, bar: 1});
var map2 = new strmap.StrMap({ bar: 2, baz: 2});
expect(map1.overrideWith(map2).toObject()).to.eql({ foo: 1, bar: 2, baz: 2 });
});
it("supplements", function () {
var map1 = new strmap.StrMap({ foo: 1, bar: 1});
var map2 = new strmap.StrMap({ bar: 2, baz: 2});
expect(map1.supplementWith(map2).toObject()).to.eql({ foo: 1, bar: 1, baz: 2 });
});
});
}
);

0 comments on commit cc2cb14

Please sign in to comment.