Updates Cascade
authordsc <dsc@wikimedia.org>
Mon, 12 Mar 2012 18:30:25 +0000 (11:30 -0700)
committerdsc <dsc@wikimedia.org>
Mon, 12 Mar 2012 18:30:25 +0000 (11:30 -0700)
lib/underscore/object.co
lib/util/cascade.co

index 1416a49..d1f4b62 100644 (file)
@@ -91,14 +91,16 @@ _obj = do
      * @param {Boolean} [ensure=false] If true, intermediate keys that are `null` or
      *  `undefined` will be filled in with a new empty object `{}`, ensuring the get will
      *   return valid metadata.
-     * @retruns {null|Object} If found, the object is of the form `{ key: Qualified key name, obj: Parent object of key, val: Value at obj[key] }`. Otherwise `null`.
+     * @retruns {undefined|Object} If found, the object is of the form 
+     *  `{ key: Qualified key name, obj: Parent object of key, val: Value at obj[key] }`. 
+     *  Otherwise `undefined`.
      */
     getNestedMeta : (obj, chain, ensure=false) ->
         chain = chain.split('.') if typeof chain is 'string'
         _.reduce do
             chain
             (current, key, idx, chain) ->
-                return null unless current?
+                return undefined unless current?
                 
                 if idx is chain.length-1
                     return
@@ -112,7 +114,7 @@ _obj = do
                 else if ensure 
                     current[key] = {}
                 else
-                    null
+                    undefined
             obj
     
     /**
@@ -138,7 +140,7 @@ _obj = do
      * @param {Boolean} [ensure=false] If true, intermediate keys that are `null` or
      *  `undefined` will be filled in with a new empty object `{}`, ensuring the set
      *  will succeed.
-     * @retruns {null|Any} If found, returns the old value, and otherwise `null`.
+     * @retruns {undefined|Any} If found, returns the old value, and otherwise `undefined`.
      */
     setNested : (obj, chain, value, ensure=false) ->
         meta = _obj.getNestedMeta obj, chain, ensure
@@ -146,7 +148,25 @@ _obj = do
             meta.obj[meta.key] = value
             meta.val
         else
-            null
+            undefined
+    
+    /**
+     * Searches a heirarchical object for a given subkey specified in
+     * dotted-property syntax, removing it if found.
+     * @param {Object} obj The object to serve as the root of the property-chain.
+     * @param {Array|String} chain The property-chain to lookup.
+     * @param {Boolean} [ensure=false] If true, intermediate keys that are `null` or
+     *  `undefined` will be filled in with a new empty object `{}`, ensuring the set
+     *  will succeed.
+     * @retruns {undefined|Any} If found, returns the old value, and otherwise `undefined`.
+     */
+    unsetNested : (obj, chain, ensure=false) ->
+        meta = _obj.getNestedMeta obj, chain, ensure
+        if meta
+            delete meta.obj[meta.key]
+            meta.val
+        else
+            undefined
     
 
 exports import _obj
index 6258ade..6b3ade0 100644 (file)
@@ -1,40 +1,62 @@
 _ = require 'kraken/underscore'
 
+
 /**
  * @class A mapping of key-value pairs supporting lookup fallback across multiple objects.
  */
 class Cascade
     
     /**
+     * Map holding the object's KV-pairs. It is also the first element of the
+     * cascade lookup.
+     * @type Object
+     * @private
+     */
+    _data : null
+    
+    /**
      * List of objects for lookups.
      * @type Array
      * @private
      */
-    _dicts : null
+    _lookups : null
+    
     
     
     /**
      * @constructor
      */
-    ->
-        @_dicts = []
-        @extend ...
+    (data={}, lookups=[]) ->
+        @_data = {} import data
+        @_lookups = [@_data].concat lookups.slice()
     
     
     /**
-     * @returns {Number} Number of real keys in the Dict.
+     * @returns {Cascade} A copy of the lookup chain.
      */
-    size : ->
-        _.keys @_dicts .length
+    clone: ->
+        new Cascade @_data, @_lookups
+    
+    
+    
+    ### Lookups ###
     
     /**
-     * @returns {Cascade} A copy of the dict, including fallbacks as well as data.
+     * Adds a new lookup dictionary to the chain.
+     * @returns {this}
      */
-    clone: ->
-        d = new Cascade
-        _.each @_dicts, (v, k) ->
-            d.setAlias k, v.slice()
-        d
+    addLookup: (dict) ->
+        @_lookups.push dict
+        this
+    
+    /**
+     * Removes a lookup dictionary from the chain.
+     * @returns {this}
+     */
+    removeLookup: (dict) ->
+        _.remove @_lookups, dict
+        this
+    
     
     
     
@@ -44,85 +66,82 @@ class Cascade
      * @returns {Boolean} Whether there is a value at the given key.
      */
     has : (key) ->
-        (@get key, null)?
+        (@get key, undefined) is not undefined
+    
     
     /**
-     * @returns {*} Ignores aliases, returning the value at key or `undefined`.
+     * @returns {*} First value for the given key found in the lookup chain,
+     * and the default otherwise.
      */
-    getValue : (key) ->
-        prop = _.getNested @_dicts, key
-        prop.value if prop?
-    
     get : (key, def) ->
-        aliases = @_aliases[key] or [key]
-        val = aliases.reduce do
-            (val, alias) ->
-                return val if val? is not undefined
-                prop = _.getNested @_dicts, alias
-                prop.value if prop?
-            undefined
-        
-        if val is not undefined
-            val
-        else
-            def
+        for data of @_lookups
+            val = _.getNested data, key, undefined
+            return val if val is not undefined
+        def
     
+    /**
+     * @param {String} key Key to set.
+     * @param {*} val Non-`undefined` value to set.
+     * @returns {this}
+     */
     set : (key, val) ->
-        _.setNested @_dicts, key, val, true
-        val
+        throw new Error("Value and key cannot be undefined!") unless key and val is not undefined
+        _.setNested @_data, key, val
+        this
     
-    del : (key) ->
-        prop = _.getNestedMeta key
-        if prop
-            delete prop.obj[prop.key]
-            prop.value
     
+    /**
+     * Delete the given key from this object's concrete data. If missing,
+     * does not cascade.
+     * @returns {undefined|*} If found, returns the old value, and otherwise `undefined`.
+     */
+    del: (key) ->
+        _.unsetNested @_data, key, false
     
     
     
     ### Collection Methods ###
     
+    extend : (...args) ->
+        for o of args
+            for k,v in o then @set k, v
+        this
+    
     toObject: ->
-        _.extend {}, ...@_dicts
+        _.extend {}, ...@_lookups
     
-    # XXX: Merge keys from all objects?
     keys: ->
-        _.keys @_data
+        _.flatten _.map @_lookups, -> _.keys it
     
     values: ->
-        _.values @_data
-    
-    extend : (...args) ->
-        for o of args
-            for k,v in o then @set k, v
-        this
+        _.flatten _.map @_lookups, -> _.values it
     
     reduce : (fn, acc, context=this) ->
-        _.reduce @_data, fn, acc, context
+        _.reduce @_lookups, fn, acc, context
     
     map : (fn, context=this) ->
-        _.map @_data, fn, context
+        _.map @_lookups, fn, context
     
     filter: (fn, context=this) ->
-        _.filter @_data, fn, context
+        _.filter @_lookups, fn, context
     
     each : (fn, context=this) ->
-        _.each @_data, fn, context
+        _.each @_lookups, fn, context
         this
     
     invoke : (name, ...args) ->
-        _.invoke @_data, name, ...args
+        _.invoke @_lookups, name, ...args
     
     pluck : (attr) ->
-        _.pluck @_data, attr
+        _.pluck @_lookups, attr
     
     find: (fn, context=this) ->
-        _.find @_data, fn, context
+        _.find @_lookups, fn, context
     
     
     
     toString: ->
-        Cls = @.constructor
+        Cls = this.constructor
         "#{Cls.displayName or Cls.name}()"