From: dsc Date: Fri, 13 Apr 2012 13:35:52 +0000 (-0700) Subject: Implements Cascade tombstones. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=2c92ad2e380a370f868bd3fd66dcaafd6e443773;p=limn.git Implements Cascade tombstones. --- diff --git a/lib/util/cascade.co b/lib/util/cascade.co index 245e2bc..15b7532 100644 --- a/lib/util/cascade.co +++ b/lib/util/cascade.co @@ -18,7 +18,7 @@ TOMBSTONE = {} */ class Cascade /** - * Default tombstone for deleted, non-passthrough keys. + * Sentinel tombstone for deleted, non-passthrough keys. * @type TOMBSTONE * @readonly */ @@ -79,12 +79,23 @@ class Cascade @_data = @_lookups[0] = data this + getTombstones: -> + @_tombstones + + # setTombstones: (tombstones) -> + # @_tombstones = tombstones + # for k, v in _.collapseObject @_tombstones + # if v + # _.setNested @_tombstones, k, TOMBSTONE, {+ensure} + # else + # _.unsetNested @_tombstones, k + # this /** * @returns {Number} Number of lookup dictionaries. */ size: -> - @_lookups.length + @_lookups.length - 1 /** * @returns {Array} The array of lookup dictionaries. @@ -139,10 +150,22 @@ class Cascade /** + * @returns {Boolean} Whether there is a tombstone set for `key`. + */ + hasTombstone: (key) -> + o = @_tombstones + for part of key.split('.') + o = o[part] + return true if o is TOMBSTONE + return false unless o + false + + /** * @returns {Boolean} Whether `key` belongs to this object (not inherited * from the cascade). */ isOwnProperty: (key) -> + return true if @hasTombstone key meta = _.getNestedMeta(@_data, key) meta?.obj and hasOwn.call meta.obj, key @@ -151,27 +174,30 @@ class Cascade * from the cascade) and is defined. */ isOwnValue: (key) -> - @isOwnProperty(key) and _.getNested(@_data, key, MISSING) is not MISSING - - /** - * @returns {Boolean} Whether the value at `key` is different from that - * inherited by from the cascade. - */ - isModifiedValue: (key, strict=false) -> - not @isInheritedValue key, strict + not @hasTombstone key + and @isOwnProperty key + and _.getNested(@_data, key, MISSING) is not MISSING /** * @returns {Boolean} Whether the value at `key` is the same as that * inherited by from the cascade. */ isInheritedValue: (key, strict=false) -> + return false if @hasTombstone key val = @get key - cVal = @_getInCascade key, MISSING, 1 + cVal = @_getInCascade key, MISSING, 2 if strict val is cVal else _.isEqual val, cVal + /** + * @returns {Boolean} Whether the value at `key` is different from that + * inherited by from the cascade. + */ + isModifiedValue: (key, strict=false) -> + not @isInheritedValue key, strict + ### Value Accessors ### @@ -185,8 +211,12 @@ class Cascade * and `def` otherwise. */ _getInCascade : (key, def, idx=0) -> - for data of @_lookups.slice(idx) - val = _.getNested data, key, MISSING + return def if @hasTombstone key + + lookups = if idx then @_lookups.slice(idx) else @_lookups + for data of lookups + val = _.getNested data, key, MISSING, {tombstone:TOMBSTONE} + return def if val is TOMBSTONE return val unless val is MISSING def @@ -228,6 +258,7 @@ class Cascade # Set and ensure the creation of missing intermediate objects. for key, val in values + _.unsetNested @_tombstones, key _.setNested @_data, key, val, {+ensure} this @@ -245,7 +276,8 @@ class Cascade * @returns {undefined|*} If found, returns the old value, and otherwise `undefined`. */ unset: (key) -> - old = _.unsetNested @_data, key + old = @get key + _.unsetNested @_data, key _.setNested @_tombstones, key, TOMBSTONE, {+ensure} old @@ -275,7 +307,10 @@ class Cascade * @returns {Object} */ collapse: -> - _.merge {}, ...@_lookups.slice().reverse() + o = _.merge {}, ...@_lookups.slice(1).reverse() + for k in @_tombstones + delete o[k] + _.merge o, @_data /** * Returns a plain object for JSON serialization via {@link Cascade#collapse()}. @@ -325,11 +360,14 @@ class Cascade "#{Cls.displayName or Cls.name}()" +# Alias methods to alternate names ALIASES = - collapse : 'toObject' - each : 'forEach' + setTombstone : 'unset' + toObject : 'collapse' + forEach : 'each' -for src, dest in ALIASES +for dest, src in ALIASES Cascade::[dest] = Cascade::[src] + module.exports = exports = Cascade diff --git a/lib/util/underscore/object.co b/lib/util/underscore/object.co index 60e8465..4e49c46 100644 --- a/lib/util/underscore/object.co +++ b/lib/util/underscore/object.co @@ -143,7 +143,7 @@ _obj = do * @param {String} [opts.setter="set"] Name of the sub-object setter method use if it exists. * @param {String} [opts.deleter="unset"] Name of the sub-object deleter method use if it exists. * @param {Object} [opts.tombstone=TOMBSTONE] Sentinel value to be interpreted as no-passthrough, - * forcing the lookup to fail and return `undefined`. + * forcing the lookup to fail and return `undefined`. TODO: opts.returnTombstone * @returns {undefined|Object} If found, the object is of the form * `{ key: Qualified key name, obj: Parent object of key, val: Value at obj[key], opts: Options }`. * Otherwise `undefined`. @@ -155,9 +155,14 @@ _obj = do _.reduce do chain - (obj, key, idx, chain) -> - return void unless obj? + (obj, key, idx) -> + return unless obj? val = _.get obj, key, void, opts + + if val is opts.tombstone + return unless ops.ensure + val = void + if idx is len return { key, val, obj, opts } if not val? and opts.ensure diff --git a/test/util/cascade-test.co b/test/util/cascade-test.co index 7b2c30a..afaebe8 100644 --- a/test/util/cascade-test.co +++ b/test/util/cascade-test.co @@ -71,6 +71,7 @@ exports.nestedKeys = -> b = lol: 'clowns' hat: fez:true + rat: drat:2, spat:2 baz: feh:2 bats: 13 @@ -89,9 +90,28 @@ exports.nestedKeys = -> assertEqual o.hat?.fez, 'red', "After nested set o.hat?.fez" assertEqual b.hat?.fez, true, "After nested set b.hat?.fez" + c.inherit 'hat.fez' + assertEqual c.get('hat.fez'), true, "After inherit c.get('hat.fez')" + assertEqual o.hat?.fez, void, "After inherit o.hat?.fez" + c.unset 'hat.fez' - assertEqual c.get('hat.fez'), true, "After unset c.get('hat.fez')" + assertEqual c.get('hat.fez'), void, "After unset c.get('hat.fez')" assertEqual o.hat?.fez, void, "After unset o.hat?.fez" + assertEqual c.hasTombstone('hat.fez'), true, "After unset c.hasTombstone('hat.fez')" + assertEqual b.hat?.fez, true, "After unset b.hat?.fez" + + c.set 'rat.drat', 1 + assertEqual c.get('rat.drat'), 1, "Shadow nested c.get('rat.drat')" + assertEqual c.get('rat.spat'), 2, "Cascade nested c.get('rat.spat')" + c.inherit 'rat' + assertEqual c.get('rat.drat'), 2, "After inherit c.get('rat.drat')" + assertEqual c.get('rat.spat'), 2, "After inherit c.get('rat.spat')" + c.unset 'rat' + assertEqual c.get('rat.drat'), void, "After unset c.get('rat.drat')" + assertEqual c.get('rat.spat'), void, "After unset c.get('rat.spat')" + assertEqual c.hasTombstone('rat.drat'), true, "After unset c.hasTombstone('rat.drat')" + assertEqual c.hasTombstone('rat.spat'), true, "After unset c.hasTombstone('rat.spat')" + assertEqual c.hasTombstone('rat'), true, "After unset c.hasTombstone('rat')"