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 +6 -14
- data/lib/ducktape/bindable.rb +24 -11
- data/lib/ducktape/bindable_attribute.rb +47 -75
- data/lib/ducktape/bindable_attribute_metadata.rb +4 -4
- data/lib/ducktape/binding_source.rb +24 -11
- data/lib/ducktape/converter.rb +23 -0
- data/lib/ducktape/expression/binary_op_exp.rb +44 -0
- data/lib/ducktape/expression/binding_parser.rb +50 -0
- data/lib/ducktape/expression/identifier_exp.rb +96 -0
- data/lib/ducktape/expression/indexer_exp.rb +74 -0
- data/lib/ducktape/expression/literal_exp.rb +34 -0
- data/lib/ducktape/expression/property_exp.rb +8 -0
- data/lib/ducktape/expression/qualified_exp.rb +8 -0
- data/lib/ducktape/link.rb +101 -0
- data/lib/ducktape/version.rb +1 -1
- data/lib/ducktape.rb +11 -0
- data/lib/ext/def_hookable.rb +4 -14
- metadata +73 -7
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
data/lib/ducktape/bindable.rb
CHANGED
@@ -62,26 +62,35 @@ module Ducktape
|
|
62
62
|
raise 'Cannot extend, only include.'
|
63
63
|
end
|
64
64
|
|
65
|
-
def
|
66
|
-
|
67
|
-
nil
|
65
|
+
def bindable_attr?(attr_name)
|
66
|
+
!!metadata(attr_name)
|
68
67
|
end
|
69
68
|
|
70
|
-
def
|
71
|
-
|
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,
|
82
|
-
return nil unless
|
83
|
-
get_bindable_attr(attr_name).remove_hook(:on_changed,
|
84
|
-
|
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
|
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
|
16
|
-
|
17
|
-
|
18
|
-
|
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,
|
19
|
+
@owner, @name, = owner, name.to_s
|
20
|
+
@source = nil
|
26
21
|
reset_value
|
27
22
|
end
|
28
23
|
|
29
|
-
def
|
30
|
-
@
|
24
|
+
def binding_source
|
25
|
+
return unless @source
|
26
|
+
@source.binding_source
|
31
27
|
end
|
32
28
|
|
33
29
|
def has_source?
|
34
|
-
|
30
|
+
!!@source
|
35
31
|
end
|
36
32
|
|
37
|
-
def
|
38
|
-
|
33
|
+
def metadata
|
34
|
+
@owner.send(:metadata, @name)
|
39
35
|
end
|
40
36
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
66
|
+
unless @source.forward?
|
67
|
+
@source.update_source
|
68
|
+
return @value #value didn't change
|
69
|
+
end
|
67
70
|
|
68
|
-
|
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
|
-
#
|
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
|
-
|
83
|
-
@value.on_changed(method(:hookable_value_changed)) if @value.is_a?(Hookable)
|
76
|
+
original_value = value
|
84
77
|
|
85
|
-
|
86
|
-
|
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
|
-
|
89
|
-
@value
|
90
|
-
end
|
83
|
+
return @value if value.equal?(@value) || value == @value # transformed value is the same?
|
91
84
|
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
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
|
-
|
117
|
-
targets_to_propagate.each { |target| target.send(:set_value, value, exclusions) }
|
118
|
-
nil
|
91
|
+
@value
|
119
92
|
end
|
120
93
|
|
121
|
-
def
|
122
|
-
|
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?(
|
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)
|
51
|
-
( v.respond_to?(
|
52
|
-
( v.is_a?(Regexp)
|
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
|
-
|
4
|
-
|
3
|
+
PROPAGATE_TO_TARGET = [:forward, :both].freeze
|
4
|
+
PROPAGATE_TO_SOURCE = [:reverse, :both].freeze
|
5
5
|
|
6
|
-
attr_reader :source
|
7
|
-
|
6
|
+
attr_reader :source, # Bindable | Object
|
7
|
+
:path, # String | Symbol
|
8
|
+
:mode, # :forward, :reverse, :both
|
9
|
+
:converter # Converter
|
8
10
|
|
9
|
-
def initialize(source,
|
10
|
-
|
11
|
-
@
|
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
|
-
|
16
|
+
def forward?
|
17
|
+
PROPAGATE_TO_TARGET.include?(@mode)
|
18
|
+
end
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
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,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
|
data/lib/ducktape/version.rb
CHANGED
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
|
data/lib/ext/def_hookable.rb
CHANGED
@@ -15,20 +15,10 @@ module Ducktape
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.hookable(obj)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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.
|
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-
|
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:
|