Various updates.

This commit is contained in:
Julien Fontanet 2014-01-11 17:00:35 +01:00
parent 084ba95b6a
commit 08a84df7d8
4 changed files with 289 additions and 252 deletions

View File

@ -6,6 +6,8 @@ $_ = require 'underscore'
#=====================================================================
# TODO: move these helpers in a dedicated module.
$done = {}
# Similar to `$_.each()` but can be interrupted by returning the
@ -28,7 +30,34 @@ $each = (col, iterator, ctx) ->
$makeFunction = (val) -> -> val
# Similar to `$_.map()` but change the current collection.
# Similar to `$_.map()` for array and `$_.mapValues()` for objects.
#
# Note: can be interrupted by returning the special value `done`
# provided as the forth argument.
$map = (col, iterator, ctx) ->
# The default context is inherited.
ctx ?= this
if (n = col.length)?
result = []
# Array-like object.
i = 0
while i < n
value = iterator.call ctx, col[i], "#{i}", col, $done
break if value is $done
result.push value
++i
else
result = {}
for key of col
value = iterator.call ctx, col[key], key, $done
break if value is $done
result.push value
# The new collection is returned.
result
# Similar to `$map()` but change the current collection.
#
# Note: can be interrupted by returning the special value `done`
# provided as the forth argument.
@ -223,27 +252,20 @@ class $MappedCollection2 extends $EventEmitter
#--------------------------------
get: (keys) ->
singular = $_.isString keys
items = $mapInPlace (@_fetchItems keys), (item) -> item.val
if singular
items[0]
if keys is undefined
items = $_.map @_byKey, (item) -> item.val
else
items
items = $mapInPlace (@_fetchItems keys), (item) -> item.val
if $_.isString keys then items[0] else items
getRaw: (keys) ->
singular = $_.isString keys
items = @_fetchItems keys
if singular
items[0]
if keys is undefined
items = item for _, item of @_byKey
else
items
items = @_fetchItems keys
getAll: ->
items = {}
items[key] = val for key, {val} of @_byKey
items
if $_.isString keys then items[0] else items
remove: (keys) ->
@_removeItems (@_fetchItems keys)
@ -339,7 +361,7 @@ class $MappedCollection2 extends $EventEmitter
# Forces items to update their value.
touch: (keys) ->
@_updateItems (@_fetchItems keys)
@_updateItems (@_fetchItems keys, true)
#--------------------------------
@ -366,17 +388,21 @@ class $MappedCollection2 extends $EventEmitter
# One for everything.
@emit "any", event, items
_fetchItems: (keys) ->
_fetchItems: (keys, ignoreMissingItems = false) ->
unless $_.isArray keys
keys = if $_.isObject keys then $_.keys keys else [keys]
items = []
for key in keys
item = @_byKey[key]
@_assert(
item?
"no item with key “#{key}"
)
item
if item?
items.push item
else
@_assert(
ignoreMissingItems
"no item with key “#{key}"
)
items
_removeItems: (items) ->
return if $_.isEmpty items

View File

@ -2,6 +2,9 @@ $_ = require 'underscore'
#=====================================================================
$asArray = (val) -> if $_.isArray val then val else [val]
$asFunction = (val) -> if $_.isFunction val then val else -> val
$removeValue = (array, value) ->
index = array.indexOf value
return false if index is -1
@ -10,24 +13,64 @@ $removeValue = (array, value) ->
#---------------------------------------------------------------------
# TODO: currently the watch can be updated multiple times per
# $MappedCollection2.set() which is inefficient: there should be
# possible to address that.
$watch = (collection, {
key
# Key(s) of the remote objects watched.
#
# If it is a function, it is evaluated in the scope of the current
# object. (TODO)
#
# Default: undefined
keys
rule
# Alias for `keys`.
key
# Rule(s) of the remote objects watched.
#
# If it is a function, it is evaluated in the scope of the current
# object. (TODO)
#
# Note: `key`/`keys` and `rule`/`rules` cannot be used both.
#
# Default: undefined
rules
# Alias for `rules`.
rule
# Value to add to the set.
#
# If it is a function, it is evaluated in the scope of the remote
# object.
#
# Default: -> @val
val
# Predicates the remote object must fulfill to be used.
#
# Default: -> true
if: cond
# Function evaluated in the scope of the remote object which
# returns the key of the object to update (usually the current one).
#
# TODO: Does it make sense to return an array?
#
# Default: undefined
bind
# Initial value.
init
}, fn) ->
# The default value is simply the value of the item.
val ?= -> @val
val = if val is undefined
# The default value is simply the value of the item.
-> @val
else
$asFunction val
watcher = {
# Method allowing the cleanup when the helper is no longer used.
@ -44,8 +87,9 @@ $watch = (collection, {
# Returns the value for this item if any or the common value.
values = watcher.values
if key of values
values["$#{key}"]
namespace = "$#{key}"
if namespace of values
values[namespace]
else
values.common
@ -61,13 +105,18 @@ $watch = (collection, {
$_.each items, (item) ->
return unless not cond? or cond.call item
# Compute the current value.
value = val.call item
if bind?
key = bind.call item
namespace = if bind?
"$#{bind.call item}"
# If bind did return a key, ignores this value.
return unless key?
namespace = "$#{key}"
else
'common'
namespace = 'common'
# Computes the current value.
value = val.call item
(valuesByNamespace[namespace] ?= []).push value
@ -99,24 +148,36 @@ $watch = (collection, {
# Sets up the watch based on the provided criteria.
#
# TODO: provides a way to clean this when no longer used.
keys ?= []
rules ?= []
keys.push key if key?
rules.push rule if rule?
keys = $asArray (keys ? key ? [])
rules = $asArray (rules ? rule ? [])
if not $_.isEmpty keys
# Matching is done on the keys.
throw new Error 'cannot use keys and rules' unless $_.isEmpty rules
$_.each keys, (key) -> collection.on "key=#{key}", processOne
else if not $_isEmpty rules
# Handles existing items.
process 'enter', collection.getRaw keys
else if not $_.isEmpty rules
# Matching is done the rules.
$_.each rules, (rule) -> collection.on "rule=#{rule}", process
# TODO: Inefficient, is there another way?
rules = do -> # Minor optimization.
tmp = Object.create null
tmp[rule] = true for rule in rules
tmp
$_.each collection.getRaw(), (item) ->
processOne 'enter', item if item.rule of rules
else
# No matching done.
collection.on 'any', updateMultiple
collection.on 'any', process
# Handles existing items.
process 'enter', collection.getRaw()
# Returns the watcher object.
watcher

View File

@ -37,189 +37,190 @@ describe 'Helper', ->
#-------------------------------------------------------------------
describe '$set', ->
describe 'with key', ->
beforeEach ->
collection.rule watcher: ->
@val = $set {
keys: [
'foo'
'bar'
]
val: -> @val
}
it 'should works with new items', ->
# Register a watcher.
collection.set {
watcher: null
}
# The set must be empty.
set = collection.get 'watcher'
$expect(set).to.have.members []
# Register a watched item.
collection.set {
foo: 'foo 1'
}
# The set must contains the value of foo.
set = collection.get 'watcher'
$expect(set).to.have.members [
collection.get 'foo'
]
# Register another watched item.
collection.set {
bar: 'bar'
}
# The set must contains the values of foo and bar.
set = collection.get 'watcher'
$expect(set).to.have.members [
collection.get 'foo'
collection.get 'bar'
]
# Updates foo and deletes bar.
collection.set {
foo: 'foo 2'
}
collection.remove 'bar'
# The set must contains the value of foo.
set = collection.get 'watcher'
$expect(set).to.have.members [
collection.get 'foo'
]
# Deletes foo.
collection.remove 'foo'
# The set must be empty.
set = collection.get 'watcher'
$expect(set).to.have.members []
it 'should works with previous items', ->
# Register watched items.
collection.set {
foo: 'foo'
bar: 'bar'
}
# Register a watcher.
collection.set {
watcher: null
}
# The set must be an array containing only the value of foo
# and bar.
set = collection.get 'watcher'
$expect(set).to.have.members [
collection.get 'foo'
collection.get 'bar'
]
#-------------------------------------------------------------------
# All helpers share the same logical code, we need only to test one
# extensively and test the others basically.
#
# $sum was chosen because it is the simplest helper to test.
describe '$sum', ->
describe 'with key', ->
it 'with single key', ->
collection.set foo: 1
beforeEach ->
collection.rule watcher: ->
@val = $sum {
keys: [
'foo'
'bar'
]
}
it 'should works with new items', ->
# Register a watcher.
collection.set {
watcher: null
collection.item sum: ->
@val = $sum {
key: 'foo'
}
# The sum must null.
sum = collection.get 'watcher'
$expect(sum).to.equal 0
$expect(collection.get 'sum').to.equal 1
# Register a watched item.
collection.set {
foo: 1
collection.set foo:2
$expect(collection.get 'sum').to.equal 2
collection.remove 'foo'
$expect(collection.get 'sum').to.equal 0
it 'with multiple keys', ->
collection.set {
foo: 1
bar: 2
}
collection.item sum: ->
@val = $sum {
keys: ['foo', 'bar']
}
# The sum must contains the value of foo.
sum = collection.get 'watcher'
$expect(sum).to.equal (collection.get 'foo')
$expect(collection.get 'sum').to.equal 3
# Register another watched item.
collection.set {
bar: 2
collection.set bar:3
$expect(collection.get 'sum').to.equal 4
collection.remove 'foo'
$expect(collection.get 'sum').to.equal 3
it 'with dynamic keys', ->
collection.set {
foo: 1
bar: 2
}
collection.rule sum: ->
@val = $sum {
key: -> (@key.split '.')[1]
}
collection.set {
'sum.foo': null
'sum.bar': null
}
$expect(collecter.get 'sum.foo').to.equal 1
$expect(collecter.get 'sum.bar').to.equal 2
collection.remove 'bar'
$expect(collecter.get 'sum.foo').to.equal 1
$expect(collecter.get 'sum.bar').to.equal 0
it 'with single rule', ->
collection.set {
'foo.1': 1
'foo.2': 2
}
collection.item sum: ->
@val = $sum {
rule: 'foo'
}
# The sum must contains the values of foo and bar.
sum = collection.get 'watcher'
$expect(sum).to.equal ((collection.get 'foo') + (collection.get 'bar'))
$expect(collection.get 'sum').to.equal 3
# Updates foo and deletes bar.
collection.set {
foo: 3
}
collection.remove 'bar'
collection.set 'foo.2':3
# The sum must contains the value of foo.
sum = collection.get 'watcher'
$expect(sum).to.equal (collection.get 'foo')
$expect(collection.get 'sum').to.equal 4
#-------------------------------------------------------------------
collection.remove 'foo.1'
describe '$val', ->
$expect(collection.get 'sum').to.equal 3
describe 'with key', ->
it 'with multiple rules', ->
collection.set {
'foo': 1
'bar.1': 2
'bar.2': 3
}
def = 'no value'
beforeEach ->
collection.rule watcher: ->
@val = $val {
keys: [
'foo'
]
default: def
}
it 'should works with new items', ->
# Register a watcher.
collection.set {
watcher: null
collection.item sum: ->
@val = $sum {
rules: ['foo', 'bar']
}
# The value must equals the default value.
value = collection.get 'watcher'
$expect(value).to.equal def
$expect(collection.get 'sum').to.equal 6
# Register a watched item.
collection.set {
foo: 'foo'
collection.set 'bar.1':3
$expect(collection.get 'sum').to.equal 7
collection.remove 'bar.2'
$expect(collection.get 'sum').to.equal 4
it 'with bind', ->
collection.set {
'foo': {
sum: 2 # This item will participate to `sum.2`.
val: 1
}
'bar': {
sum: 1 # This item will participate to `sum.1`.
val: 2
}
}
collection.rule sum: ->
@val = $sum {
bind: ->
id = @val.sum
return unless id?
"sum.#{id}"
val: -> @val.val
}
collection.set {
'sum.1': null
'sum.2': null
}
$expect(collection.get 'sum.1').equal 2
$expect(collection.get 'sum.2').equal 1
it 'with predicate', ->
collection.set {
foo: 1
bar: 2
baz: 3
}
collection.item sum: ->
@val = $sum {
if: -> /^b/.test @rule
}
# The value must contains the value of foo.
value = collection.get 'watcher'
$expect(value).to.equal (collection.get 'foo')
$expect(collection.get 'sum').equal 5
# Deletes foo.
collection.remove 'foo'
collection.set foo:4
# The value must equals the default value.
value = collection.get 'watcher'
$expect(value).to.equal def
$expect(collection.get 'sum').equal 5
collection.set bar:5
$expect(collection.get 'sum').equal 8
collection.remove 'baz'
$expect(collection.get 'sum').equal 5
it 'with initial value', ->
collection.set foo: 1
collection.item sum: ->
@val = $sum {
key: 'foo'
init: 2
}
$expect(collection.get 'sum').to.equal 3
collection.set foo:2
$expect(collection.get 'sum').to.equal 4
collection.remove 'foo'
$expect(collection.get 'sum').to.equal 2
# TODO:
# - bind
# - dynamic key
# - handle previously existing items.
# - dynamic keys
# - dynamic rules

View File

@ -1,54 +1,3 @@
# FIXME: function executed each time vs dynamic fields.
$set = ({
# Identifier of the remote object watched.
#
# If it is a function, it is evaluated in the scope of the current
# object.
#
# TODO: Can also be an array.
#
# Default: undefined
key
# Rule to watch (everything if null/undefined).
#
# Default: undefined
rule
# Value to add to the set.
#
# If it is a function, it is evaluated in the scope of the remote
# object.
#
# Default: -> @key
val
# Predicates which must be fulfilled for the remote object to be
# used.
#
# Default: true
if: cond
# Identifier of the object to update (usually the current one).
#
# Usually this entry is a function which is evaluated in the scope
# of the remote object.
#
# TODO: Can also be an array.
#
# Default: undefined
bind
}) ->
val ?= -> @key
# TODO
$sum = ({rule, val, if: cond, bind}) ->
# TODO
$map = ({key, rule, val, if: cond, bind}) ->
# TODO
isVMRunning = ->
switch @power_state
when 'Paused', 'Running'