ducktape 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NTdhMjYxMTNjNWZmOTYwOWEwZjI0ZGU2OTQxOTYyYTgxYTUzOTQwNw==
5
- data.tar.gz: !binary |-
6
- ZTViZThkYWZiNGRiNjI3NGRmZTg1NTUzMzc3ZjIwMGEwNzVjYjc5MA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MWIxOWMxOWVkNWZlMWFlNzA4OWUzMDU5MDljNzBhNWI4ZDBlZTJkZWUyN2Ji
10
- M2Y5NmJiN2UxZmZjMmIxNWM0ZmI0OTM2MWQyZTE4MDg5ODdlZTQxYTU3N2Ez
11
- YmVkM2EzMjU4MDViMmY0M2RhNDgxYTgyNmM4ZDg1MDU0NmVlZWM=
12
- data.tar.gz: !binary |-
13
- YTk1NTYxN2NjNDgwMmI2NTJkMmU4MGQyNTYzZjFlNDU1NzdkOGQ5MDJlNjc3
14
- ZDQwM2M2MDUzOTkyN2QyNmU0YTk4ZjhjOTJkNDhiZGZmOThkOWIyYjU3ZmYz
15
- MzhjOWM5NDhhODdkZWIwZjBlNTI3N2NmOTFlNzAzNDgzYTY1MmU=
2
+ SHA1:
3
+ metadata.gz: ae5e41fb6852b1d6c317f3cb72d2615ee2ad04a8
4
+ data.tar.gz: a3aa58766760da4d1f6363f7727db35cff8ad1b1
5
+ SHA512:
6
+ metadata.gz: 19a4061eda2662fbab5e27529fa04b1bf4e70fdde0789bdda80d71b3a01aea7cddd84b3e98f81022dc265295c9adc5f619c05d711de6e6e420790ffb80fce637
7
+ data.tar.gz: e74ce291ea4b9c8758c6688ef195bc754818b306b0cedaf531fc8ad84ae9916eb7580ecb1aeb69c02d8b1dbd21cfee5fc7c27b98da7f0515eb98cae5e4fa86dc
@@ -62,26 +62,35 @@ module Ducktape
62
62
  raise 'Cannot extend, only include.'
63
63
  end
64
64
 
65
- def unbind_source(attr_name)
66
- get_bindable_attr(attr_name).remove_source
67
- nil
65
+ def bindable_attr?(attr_name)
66
+ !!metadata(attr_name)
68
67
  end
69
68
 
70
- def clear_bindings()
71
- bindable_attrs.each { |_, attr| attr.remove_source }
69
+ def binding_source(attr_name)
70
+ return unless bindable_attr?(attr_name)
71
+ get_bindable_attr(attr_name).binding_source
72
+ end
73
+
74
+ def clear_bindings(reset = true)
75
+ bindable_attrs.each { |_, attr| attr.remove_source(reset) }
72
76
  nil
73
77
  end
74
78
 
75
79
  def on_changed(attr_name, hook = nil, &block)
76
80
  return nil unless block || hook
77
81
  get_bindable_attr(attr_name).on_changed(hook, &block)
78
- block
82
+ hook || block
83
+ end
84
+
85
+ def unbind_source(attr_name)
86
+ get_bindable_attr(attr_name).remove_source
87
+ nil
79
88
  end
80
89
 
81
- def unhook_on_changed(attr_name, block)
82
- return nil unless block
83
- get_bindable_attr(attr_name).remove_hook(:on_changed, block)
84
- block
90
+ def unhook_on_changed(attr_name, hook)
91
+ return nil unless hook
92
+ get_bindable_attr(attr_name).remove_hook(:on_changed, hook)
93
+ hook
85
94
  end
86
95
 
87
96
  protected #--------------------------------------------------------------
@@ -90,6 +99,10 @@ module Ducktape
90
99
  get_bindable_attr(attr_name).value
91
100
  end
92
101
 
102
+ def metadata(name)
103
+ is_a?(Class) ? singleton_class.metadata(name) : self.class.metadata(name)
104
+ end
105
+
93
106
  def set_value(attr_name, value)
94
107
  get_bindable_attr(attr_name).value = value
95
108
  end
@@ -101,7 +114,7 @@ module Ducktape
101
114
  end
102
115
 
103
116
  def get_bindable_attr(name)
104
- raise AttributeNotDefinedError.new(self.class, name.to_s) unless self.class.metadata(name)
117
+ raise AttributeNotDefinedError.new(self.class, name.to_s) unless bindable_attr?(name)
105
118
  bindable_attrs[name.to_s] ||= BindableAttribute.new(self, name)
106
119
  end
107
120
  end
@@ -1,5 +1,3 @@
1
- autoload :Set, 'set'
2
-
3
1
  module Ducktape
4
2
 
5
3
  class InvalidAttributeValueError < StandardError
@@ -12,115 +10,89 @@ module Ducktape
12
10
 
13
11
  include Hookable
14
12
 
15
- attr_reader :owner # Bindable
16
- attr_reader :name # String
17
- attr_reader :source # BindingSource
18
- attr_reader :value # Object
19
-
20
- #attr_reader :targets # Hash{ BindableAttribute => BindingSource }
21
-
22
- #def_hook :on_changed
13
+ attr_reader :owner, # Bindable
14
+ :name, # String
15
+ :value # Object
16
+ #:source # Link - link between source and target
23
17
 
24
18
  def initialize(owner, name)
25
- @owner, @name, @source, @targets = owner, name.to_s, nil, {}
19
+ @owner, @name, = owner, name.to_s
20
+ @source = nil
26
21
  reset_value
27
22
  end
28
23
 
29
- def metadata
30
- @owner.class.metadata(@name)
24
+ def binding_source
25
+ return unless @source
26
+ @source.binding_source
31
27
  end
32
28
 
33
29
  def has_source?
34
- !@source.nil?
30
+ !!@source
35
31
  end
36
32
 
37
- def value=(value)
38
- set_value(value)
33
+ def metadata
34
+ @owner.send(:metadata, @name)
39
35
  end
40
36
 
41
- #If values are equal, it means that both attributes share the same value,
42
- #if they are different, the values are independent.
43
- #As such, self's value is reset if mode is both, or if mode is forward and values are equal;
44
- #the source's value is reset of mode is reverse and values are equal.
45
- def remove_source
46
- return unless has_source?
47
- bs, v = detach_source
48
- reset_value if bs.mode == :both || (bs.mode == :forward && v == @value)
49
- bs
37
+ #After unbinding the source the value can be reset, or kept.
38
+ #The default is to reset the target's (self) value.
39
+ def remove_source(reset = true)
40
+ return unless @source
41
+ src, @source = @source, nil
42
+ src.unbind
43
+ reset_value if reset
44
+ src.binding_source
50
45
  end
51
46
 
52
47
  def reset_value
53
48
  set_value(metadata.default)
54
49
  end
55
50
 
51
+ def value=(value)
52
+ set_value(value)
53
+ end
54
+
56
55
  def to_s
57
56
  "#<#{self.class}:0x#{object_id.to_s(16)} @name=#{name}>"
58
57
  end
59
58
 
60
59
  private #----------------------------------------------------------------
61
60
 
62
- attr_reader :targets
61
+ def set_value(value)
62
+ if value.is_a?(BindingSource) #attach new binding source
63
+ remove_source(false)
64
+ @source = Link.new(value, self).tap { |l| l.bind }
63
65
 
64
- def set_value(value, exclusions = Set.new)
65
- return if exclusions.include? self
66
- exclusions << self
66
+ unless @source.forward?
67
+ @source.update_source
68
+ return @value #value didn't change
69
+ end
67
70
 
68
- if value.is_a? BindingSource
69
- attach_source(value) #attach new binding source
70
- exclusions << @source.source #update value
71
- value = @source.source.value #new value is the new source value
71
+ value = @source.source_value
72
72
  end
73
73
 
74
- #set effective value
75
- if @value != value
76
- m = metadata
77
- value = m.coerce(owner, value)
78
- raise InvalidAttributeValueError.new(@name, value) unless m.validate(value)
79
- old_value = @value
80
- @value = value
74
+ return @value if value.equal?(@original_value) || value == @original_value # untransformed value is the same?
81
75
 
82
- old_value.remove_hook(:on_changed, method(:hookable_value_changed)) if old_value.is_a?(Hookable)
83
- @value.on_changed(method(:hookable_value_changed)) if @value.is_a?(Hookable)
76
+ original_value = value
84
77
 
85
- call_hooks(:on_changed, owner, attribute: name.dup, value: @value, old_value: old_value)
86
- end
78
+ # transform value
79
+ m = metadata
80
+ value = m.coerce(owner, value)
81
+ raise InvalidAttributeValueError.new(@name, value) unless m.validate(value)
87
82
 
88
- propagate_value(exclusions)
89
- @value
90
- end
83
+ return @value if value.equal?(@value) || value == @value # transformed value is the same?
91
84
 
92
- def detach_source
93
- return unless @source
94
- bs = @source
95
- v = bs.source.value
96
- @source = nil
97
- bs.source.send(:targets).delete(self)
98
- bs.source.reset_value if bs.mode == :reverse && v == @value
99
- [bs, v]
100
- end
101
-
102
- # source: BindingSource
103
- def attach_source(source)
104
- detach_source
105
- @source = source
106
- source.source.send(:targets)[self] = source
107
- nil
108
- end
85
+ #set effective value
86
+ old_value, @value, @original_value = @value, value, original_value
87
+ call_hooks(:on_changed, owner, attribute: name.dup, value: @value, old_value: old_value)
109
88
 
110
- def targets_to_propagate
111
- targets = []
112
- targets << @source.source if @source && BindingSource::PROPAGATE_TO_SOURCE.member?(@source.mode)
113
- targets.concat(@targets.values.select { |b| BindingSource::PROPAGATE_TO_TARGETS.member?(b.mode) })
114
- end
89
+ @source.update_source if @source && @source.reverse?
115
90
 
116
- def propagate_value(exclusions)
117
- targets_to_propagate.each { |target| target.send(:set_value, value, exclusions) }
118
- nil
91
+ @value
119
92
  end
120
93
 
121
- def hookable_value_changed(*_)
122
- call_hooks('on_changed', owner, attribute: name, value: @value)
123
- nil
94
+ def convert(value)
95
+ value
124
96
  end
125
97
  end
126
98
  end
@@ -36,7 +36,7 @@ module Ducktape
36
36
  end
37
37
 
38
38
  def default
39
- @default.respond_to?('call') ? @default.call : @default
39
+ @default.respond_to?(:call) ? @default.call : @default
40
40
  end
41
41
 
42
42
  def validation(*options, &block)
@@ -47,9 +47,9 @@ module Ducktape
47
47
  def validate(value)
48
48
  return true unless @validation
49
49
  @validation.each do |v|
50
- return true if ( v.is_a?(Class) && value.is_a?(v) ) ||
51
- ( v.respond_to?('call') && v.(value) ) ||
52
- ( v.is_a?(Regexp) && value =~ v ) ||
50
+ return true if ( v.is_a?(Class) && value.is_a?(v) ) ||
51
+ ( v.respond_to?(:call) && v.(value) ) ||
52
+ ( v.is_a?(Regexp) && value =~ v ) ||
53
53
  value == v
54
54
  end
55
55
  false
@@ -1,21 +1,34 @@
1
1
  module Ducktape
2
2
  class BindingSource
3
- PROPAGATE_TO_SOURCE = [:reverse, :both].freeze
4
- PROPAGATE_TO_TARGETS = [:forward, :both].freeze
3
+ PROPAGATE_TO_TARGET = [:forward, :both].freeze
4
+ PROPAGATE_TO_SOURCE = [:reverse, :both].freeze
5
5
 
6
- attr_reader :source # BindableAttribute
7
- attr_accessor :mode # :forward, :reverse, :both
6
+ attr_reader :source, # Bindable | Object
7
+ :path, # String | Symbol
8
+ :mode, # :forward, :reverse, :both
9
+ :converter # Converter
8
10
 
9
- def initialize(source, source_attr, mode = :both)
10
- set_source(source, source_attr)
11
- @mode = mode
11
+ def initialize(source, path, mode = :both, converter = Converter)
12
+ @source, @path, @mode = source, path, mode
13
+ @converter = make_converter(converter)
12
14
  end
13
15
 
14
- private
16
+ def forward?
17
+ PROPAGATE_TO_TARGET.include?(@mode)
18
+ end
15
19
 
16
- #TODO: notify source/target of change
17
- def set_source(source, source_attr)
18
- @source = source.send(:get_bindable_attr, source_attr)
20
+ def reverse?
21
+ PROPAGATE_TO_SOURCE.include?(@mode)
22
+ end
23
+
24
+ private
25
+ def make_converter(c)
26
+ case c
27
+ when nil then Converter
28
+ when Class then (c.respond_to?(:convert) && c.respond_to?(:revert)) ? c : c.new
29
+ when Proc, Method, Array then Converter.new(*c)
30
+ else c
31
+ end
19
32
  end
20
33
  end
21
34
  end
@@ -0,0 +1,23 @@
1
+ module Ducktape
2
+ class Converter
3
+ def initialize(convert = ->(v){v}, revert = nil)
4
+ @cnv, @rev = convert, revert || convert
5
+ end
6
+
7
+ def convert(value)
8
+ @cnv.(value)
9
+ end
10
+
11
+ def revert(value)
12
+ @rev.(value)
13
+ end
14
+
15
+ def self.convert(value)
16
+ value
17
+ end
18
+
19
+ def self.revert(value)
20
+ value
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,44 @@
1
+ module Ducktape
2
+ module Expression
3
+ module BinaryOpExp
4
+ attr_reader :left, :right
5
+ attr_reader :owner
6
+
7
+ def initialize(left, right)
8
+ @left, @right = left, right
9
+ end
10
+
11
+ def owner=(o)
12
+ @owner, left.owner, right.owner = [o]*3
13
+ end
14
+
15
+ def bind(src, type, qual = nil, root = src)
16
+ unbind
17
+ lsrc = left.bind(src, :path, qual, root)
18
+ right.bind(lsrc, type, self.class, root)
19
+ end
20
+
21
+ def unbind
22
+ left.unbind
23
+ right.unbind
24
+ nil
25
+ end
26
+
27
+ def rightmost
28
+ @right.rightmost
29
+ end
30
+
31
+ def unparse
32
+ "#{left.unparse}#{self.class.instance_variable_get(:@op)}#{right.unparse}"
33
+ end
34
+
35
+ def value
36
+ right.value
37
+ end
38
+
39
+ def value=(v)
40
+ right.value = v
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ module Ducktape
2
+ module Expression
3
+ class UnboundError < StandardError; end
4
+
5
+ class BindingParser < Whittle::Parser
6
+ %w':: . , [ ]'.each { |op| rule(op) }
7
+
8
+ rule(number: /[0-9]+/).as { |v| IntegerExp.new(Integer(v)) }
9
+ rule(id: /[a-zA-Z_]\w*/).as { |v| IdentifierExp.new(v) }
10
+ rule(symbol: /:[a-zA-Z_]\w*/).as { |v| SymbolExp.new(v[1,v.length].to_sym) }
11
+
12
+ rule(:qual) do |r|
13
+ r[:indexer, '::', :expr].as { |l, _, r| QualifiedExp.new(l, r) }
14
+ r[:id, '::', :expr].as { |l, _, r| QualifiedExp.new(l, r) }
15
+ end
16
+
17
+ rule(:property) do |r|
18
+ r[:indexer, '.', :expr].as { |l, _, r| PropertyExp.new(l, r) }
19
+ r[:id, '.', :expr].as { |l, _, r| PropertyExp.new(l, r) }
20
+ end
21
+
22
+ rule(:params) do |r|
23
+ r[:params, ',', :expr].as { |params, _, v| params << v }
24
+ r[:params, ',', :symbol].as { |params, _, v| params << v }
25
+ r[:params, ',', :number].as { |params, _, v| params << v }
26
+ r[:expr].as { |v| [v] }
27
+ r[:symbol].as { |v| [v] }
28
+ r[:number].as { |v| [v] }
29
+ end
30
+
31
+ rule(:indexer) do |r|
32
+ r[:indexer, '[', :params, ']'].as { |l, _, r, _| IndexerExp.new(l, r) }
33
+ r[:id, '[', :params, ']'].as { |l, _, r, _| IndexerExp.new(l, r) }
34
+ end
35
+
36
+ rule(:expr) do |r|
37
+ r[:qual]
38
+ r[:property]
39
+ r[:indexer]
40
+ r[:id]
41
+ end
42
+
43
+ start :expr
44
+
45
+ def self.parse(input, options = {})
46
+ new.parse(input.to_s, options)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,96 @@
1
+ module Ducktape
2
+ module Expression
3
+ class IdentifierExp
4
+ include LiteralExp
5
+ include Ref #WeakReference
6
+
7
+ def bind(src, type, qual = nil, _ = src)
8
+ unbind
9
+ @source, @type, @qual = WeakReference.new(src), type, qual
10
+
11
+ case
12
+ when src.is_a?(Bindable) && src.bindable_attr?(literal) then src.on_changed(literal, self)
13
+ when src.is_a?(Hookable) then src.on_changed(self)
14
+ end
15
+
16
+ value
17
+ end
18
+
19
+ def unbind
20
+ return unless @source
21
+ src = @source.object
22
+ return @source, @type, @qual = nil unless src
23
+
24
+ case
25
+ when src.is_a?(Bindable) && src.bindable_attr?(literal) then src.unhook_on_changed(literal, self)
26
+ when src.is_a?(Hookable) then src.remove_hook(:on_changed, self)
27
+ end
28
+
29
+ @source, @type, @qual = nil
30
+ end
31
+
32
+ def call(parms)
33
+ return unless parms[:attribute].to_s == literal.to_s
34
+ owner.send("#{@type}_changed")
35
+ nil
36
+ end
37
+
38
+ def rightmost
39
+ self
40
+ end
41
+
42
+ def unparse
43
+ literal
44
+ end
45
+
46
+ def value
47
+ src = source
48
+ case @qual
49
+ when QualifiedExp then src.const_get(literal)
50
+ when PropertyExp then src.public_send(literal)
51
+ else is_constant?(src) ? src.const_get(literal) : src.public_send(literal)
52
+ end
53
+ end
54
+
55
+ def value=(value)
56
+ src = source
57
+ return unless @type == :value
58
+
59
+ case @qual
60
+ when QualifiedExp then src.const_set(literal, value)
61
+ when PropertyExp then property_set(src, value)
62
+ else is_constant?(src) ? src.const_set(literal, value) : property_set(src, value)
63
+ end
64
+
65
+ nil
66
+ end
67
+
68
+ protected
69
+ def source
70
+ raise UnboundError unless @source
71
+ src = @source.object
72
+ raise UnboundError unless src
73
+ src
74
+ end
75
+
76
+ private
77
+ def is_constant?(src)
78
+ literal =~ /^[A-Z]/ && # starts with capital?
79
+ src.respond_to?(:const_defined?) && # source can check for constants (Class, Module)?
80
+ src.const_defined?(literal) # check to see if constant exists
81
+ end
82
+
83
+ def property_set(src, value)
84
+ case
85
+ when src.is_a?(Bindable) && src.bindable_attr?(literal)
86
+ src.send(:get_bindable_attr, literal).value = value
87
+ when src.respond_to?("#{literal}=")
88
+ src.public_send("#{literal}=", value)
89
+ when src.respond_to?(literal) && [-2, -1, 1].include?(src.public_method(literal).arity)
90
+ src.public_send(literal, value)
91
+ else nil # nothing to do
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,74 @@
1
+ module Ducktape
2
+ module Expression
3
+ class IndexerExp # left[right+]
4
+ include BinaryOpExp
5
+ include Ref
6
+
7
+ alias_method :params, :right
8
+
9
+ def bind(src, type, qual = nil, root = src)
10
+ unbind
11
+ src = left.bind(src, :path, qual, root)
12
+ @source = WeakReference.new(src)
13
+ @type = type
14
+ params.each { |param| param.bind(root, :path) }
15
+
16
+ src.on_store(self) if src.is_a?(Hookable) && src.respond_to?(:on_store) #convention hook for []=
17
+
18
+ value
19
+ end
20
+
21
+ def unbind
22
+ params.each { |param| param.unbind }
23
+
24
+ return unless @source
25
+ src = @source.object
26
+ return @source, @type, @qual = nil unless src
27
+
28
+ src = source
29
+ src.remove_hook(:on_store, self) if src.is_a?(Hookable) && src.respond_to?(:on_store)
30
+ nil
31
+ end
32
+
33
+ def call(parms)
34
+ parms = parms.args.dup
35
+ parms.pop
36
+ return unless parms == params_values
37
+ owner.send("#{@type}_changed")
38
+ end
39
+
40
+ def owner=(o)
41
+ @owner, left.owner = o, o
42
+ params.each { |param| param.owner = o }
43
+ end
44
+
45
+ def rightmost
46
+ self
47
+ end
48
+
49
+ def unparse
50
+ "#{left.unparse}[#{params.map(&:unparse).join(',')}]"
51
+ end
52
+
53
+ def value
54
+ source[*params_values]
55
+ end
56
+
57
+ def value=(v)
58
+ source[*params_values] = v
59
+ end
60
+
61
+ protected
62
+ def source
63
+ raise UnboundError unless @source
64
+ src = @source.object
65
+ raise UnboundError unless src
66
+ src
67
+ end
68
+
69
+ def params_values
70
+ params.map { |param| param.value }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,34 @@
1
+ module Ducktape
2
+ module Expression
3
+ module LiteralExp
4
+ attr_reader :literal
5
+ attr_accessor :owner
6
+
7
+ def initialize(literal)
8
+ @literal = literal
9
+ @literal.freeze
10
+ end
11
+
12
+ def bind(_, _) end
13
+ def unbind; end
14
+
15
+ def rightmost
16
+ nil
17
+ end
18
+
19
+ def unparse
20
+ literal.inspect
21
+ end
22
+
23
+ alias_method :value, :literal
24
+ end
25
+
26
+ class IntegerExp
27
+ include LiteralExp
28
+ end
29
+
30
+ class SymbolExp
31
+ include LiteralExp
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,8 @@
1
+ module Ducktape
2
+ module Expression
3
+ class PropertyExp #a.b
4
+ include BinaryOpExp
5
+ @op = '.'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Ducktape
2
+ module Expression
3
+ class QualifiedExp #A::B
4
+ include BinaryOpExp
5
+ @op = '::'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,101 @@
1
+ module Ducktape
2
+ class Link
3
+ include Ref #WeakReference
4
+
5
+ class ModeError < StandardError; end
6
+
7
+ class << self
8
+ def cleanup(*method_names)
9
+ method_names.each do |method_name|
10
+ m = instance_method(method_name)
11
+ define_method(method_name, ->(*args, &block) do
12
+ return m.bind(self).(*args, &block) unless broken?
13
+ unbind if @expression
14
+ @target.object.remove_source if @target && @target.object
15
+ @source, @target, @expression = nil
16
+ end)
17
+ end
18
+ end
19
+ end
20
+
21
+ attr_accessor :source, # WeakRef of Object
22
+ :expression, # Expression (e.g.: 'a::X.b[c,d]')
23
+ :target, # WeakRef of BindingAttribute
24
+ :converter, # Method
25
+ :mode # :forward, :reverse, :both
26
+
27
+ attr_reader :binding_source
28
+
29
+ def initialize(binding_source, target)
30
+ @binding_source = binding_source
31
+ @source = WeakReference.new(binding_source.source)
32
+ @expression = Expression::BindingParser.parse(binding_source.path)
33
+ @target = WeakReference.new(target)
34
+ @converter = binding_source.converter
35
+ @mode = binding_source.mode
36
+
37
+ @expression.owner = self
38
+ end
39
+
40
+ def broken?
41
+ !(@source && @target && @source.object && @target.object)
42
+ end
43
+
44
+ def forward?
45
+ BindingSource::PROPAGATE_TO_TARGET.include?(@mode)
46
+ end
47
+
48
+ def reverse?
49
+ BindingSource::PROPAGATE_TO_SOURCE.include?(@mode)
50
+ end
51
+
52
+ def bind
53
+ @expression.bind(@source.object, :value)
54
+ nil
55
+ end
56
+
57
+ def unbind
58
+ @expression.unbind
59
+ nil
60
+ end
61
+
62
+ def update_source
63
+ assert_mode :set, :source, :reverse
64
+ @expression.value = target_value
65
+ end
66
+
67
+ def update_target
68
+ assert_mode :set, :target, :forward
69
+ @target.object.value = source_value
70
+ end
71
+
72
+ def source_value
73
+ assert_mode :get, :source, :forward
74
+ @converter.convert(@expression.value)
75
+ end
76
+
77
+ def target_value
78
+ assert_mode :get, :target, :reverse
79
+ @converter.revert(@target.object.value)
80
+ end
81
+
82
+ cleanup :bind, :unbind, :update_source, :update_target, :source_value, :target_value
83
+
84
+ private
85
+ def assert_mode(accessor, type, mode)
86
+ raise ModeError, "cannot #{accessor} #{type} value on a non #{mode} link" unless public_send("#{mode}?")
87
+ end
88
+
89
+ def path_changed
90
+ bind
91
+ forward? ? update_target : update_source
92
+ nil
93
+ end
94
+
95
+ def value_changed
96
+ return unless forward?
97
+ update_target
98
+ nil
99
+ end
100
+ end
101
+ end
@@ -3,5 +3,5 @@
3
3
  # represented by a major version number increase.
4
4
 
5
5
  module Ducktape
6
- VERSION = '0.3.3'.freeze
6
+ VERSION = '0.4.0'.freeze
7
7
  end
data/lib/ducktape.rb CHANGED
@@ -1,11 +1,22 @@
1
1
  %w'
2
2
  set
3
3
  facets/ostruct
4
+ ref
5
+ whittle
4
6
  '.each { |f| require f }
5
7
 
6
8
  %w'
7
9
  version
8
10
  hookable
11
+ expression/literal_exp
12
+ expression/binary_op_exp
13
+ expression/identifier_exp
14
+ expression/indexer_exp
15
+ expression/property_exp
16
+ expression/qualified_exp
17
+ expression/binding_parser
18
+ converter
19
+ link
9
20
  binding_source
10
21
  bindable_attribute_metadata
11
22
  bindable_attribute
@@ -15,20 +15,10 @@ module Ducktape
15
15
  end
16
16
 
17
17
  def self.hookable(obj)
18
- unless obj.is_a? Hookable
19
- m = obj.class.ancestors.each do |c|
20
- v = @hookable_types[c]
21
- break v if v
22
- end
23
-
24
- if m
25
- (class << obj
26
- include Hookable
27
- self
28
- end).make_hooks(m)
29
- end
30
- end
31
-
18
+ return obj if obj.is_a?(Hookable)
19
+ m = obj.class.ancestors.each { |c| v = @hookable_types[c]; break v if v }
20
+ return obj unless m
21
+ (class << obj; include Hookable; self end).make_hooks(m)
32
22
  obj
33
23
  end
34
24
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ducktape
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SilverPhoenix99
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-08 00:00:00.000000000 Z
12
+ date: 2013-03-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: facets
@@ -25,6 +25,48 @@ dependencies:
25
25
  - - ~>
26
26
  - !ruby/object:Gem::Version
27
27
  version: '2.9'
28
+ - !ruby/object:Gem::Dependency
29
+ name: whittle
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '0.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '0.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: ref
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '1'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '1'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
28
70
  description: Truly outrageous bindable attributes
29
71
  email:
30
72
  - silver.phoenix99@gmail.com
@@ -37,29 +79,54 @@ files:
37
79
  - lib/ducktape/bindable_attribute.rb
38
80
  - lib/ducktape/bindable_attribute_metadata.rb
39
81
  - lib/ducktape/binding_source.rb
82
+ - lib/ducktape/converter.rb
83
+ - lib/ducktape/expression/binary_op_exp.rb
84
+ - lib/ducktape/expression/binding_parser.rb
85
+ - lib/ducktape/expression/identifier_exp.rb
86
+ - lib/ducktape/expression/indexer_exp.rb
87
+ - lib/ducktape/expression/literal_exp.rb
88
+ - lib/ducktape/expression/property_exp.rb
89
+ - lib/ducktape/expression/qualified_exp.rb
40
90
  - lib/ducktape/ext/array.rb
41
91
  - lib/ducktape/ext/hash.rb
42
92
  - lib/ducktape/ext/string.rb
43
93
  - lib/ducktape/hookable.rb
94
+ - lib/ducktape/link.rb
44
95
  - lib/ducktape/version.rb
45
96
  - lib/ducktape.rb
46
97
  - lib/ext/def_hookable.rb
47
98
  - README.md
48
99
  homepage: https://github.com/SilverPhoenix99/ducktape
49
- licenses: []
100
+ licenses:
101
+ - MIT
50
102
  metadata: {}
51
- post_install_message:
103
+ post_install_message: |
104
+ +----------------------------------------------------------------------------+
105
+ Thank you for choosing Ducktape.
106
+
107
+ ==========================================================================
108
+ 0.4.0 Changes:
109
+ - This version is compatible with version 0.3.x.
110
+ - Added path expression to binding sources.
111
+
112
+ If you like what you see, support us on Pledgie:
113
+ http://www.pledgie.com/campaigns/18955
114
+
115
+ If you find any bugs, please report them on
116
+ https://github.com/SilverPhoenix99/ducktape/issues
117
+
118
+ +----------------------------------------------------------------------------+
52
119
  rdoc_options: []
53
120
  require_paths:
54
121
  - lib
55
122
  required_ruby_version: !ruby/object:Gem::Requirement
56
123
  requirements:
57
- - - ! '>='
124
+ - - '>='
58
125
  - !ruby/object:Gem::Version
59
126
  version: '0'
60
127
  required_rubygems_version: !ruby/object:Gem::Requirement
61
128
  requirements:
62
- - - ! '>='
129
+ - - '>='
63
130
  - !ruby/object:Gem::Version
64
131
  version: '0'
65
132
  requirements: []
@@ -69,4 +136,3 @@ signing_key:
69
136
  specification_version: 4
70
137
  summary: Truly outrageous bindable attributes
71
138
  test_files: []
72
- has_rdoc: