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 +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:
|