ducktape 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: