ducktape 0.0.5 → 0.1.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.
- data/README.md +400 -0
- data/lib/ducktape/bindable.rb +1 -0
- data/lib/ducktape/bindable_attribute_metadata.rb +54 -49
- data/lib/ducktape/hookable.rb +9 -0
- data/lib/ducktape/hookable_array.rb +50 -1
- data/lib/ducktape/version.rb +3 -0
- data/lib/ducktape.rb +5 -4
- metadata +5 -3
data/README.md
ADDED
@@ -0,0 +1,400 @@
|
|
1
|
+
Ducktape
|
2
|
+
========
|
3
|
+
|
4
|
+
A [truly outrageous](http://youtu.be/dSPb56-_I98) gem for bindable attributes.
|
5
|
+
|
6
|
+
To install:
|
7
|
+
|
8
|
+
```
|
9
|
+
gem install ducktape
|
10
|
+
```
|
11
|
+
|
12
|
+
Bindable attributes
|
13
|
+
-------------------
|
14
|
+
|
15
|
+
Bindable attributes (BA) work just like normal attributes. To assign a BA to a class you just need to declare it like an attr_accessor:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'ducktape'
|
19
|
+
|
20
|
+
class X
|
21
|
+
include Ducktape::Bindable
|
22
|
+
|
23
|
+
bindable :name
|
24
|
+
|
25
|
+
def initialize(name)
|
26
|
+
self.name = name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
### Binding
|
32
|
+
|
33
|
+
BA's, like the name hints, can be bound to other BA's:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class X
|
37
|
+
include Ducktape::Bindable
|
38
|
+
|
39
|
+
bindable :name
|
40
|
+
end
|
41
|
+
|
42
|
+
class Y
|
43
|
+
include Ducktape::Bindable
|
44
|
+
|
45
|
+
bindable :other_name
|
46
|
+
end
|
47
|
+
|
48
|
+
x = X.new
|
49
|
+
|
50
|
+
y = Y.new
|
51
|
+
y.other_name = Ducktape::BindingSource.new(x, :name)
|
52
|
+
|
53
|
+
x.name = 'Richard'
|
54
|
+
|
55
|
+
puts y.other_name
|
56
|
+
```
|
57
|
+
|
58
|
+
Output:
|
59
|
+
```ruby
|
60
|
+
=> "Richard"
|
61
|
+
```
|
62
|
+
|
63
|
+
There are three types of direction for `BindingSource`:
|
64
|
+
* `:both` - (default) Changes apply in both directions.
|
65
|
+
* `:forward` - Changes only apply from the binding source BA to the destination BA.
|
66
|
+
* `:reverse` - Changes only apply from the destination BA to the binding source BA.
|
67
|
+
|
68
|
+
Here's an example of `:reverse` binding (very similar to the previous):
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class X
|
72
|
+
include Ducktape::Bindable
|
73
|
+
|
74
|
+
bindable :name
|
75
|
+
end
|
76
|
+
|
77
|
+
class Y
|
78
|
+
include Ducktape::Bindable
|
79
|
+
|
80
|
+
bindable :other_name
|
81
|
+
end
|
82
|
+
|
83
|
+
x = X.new
|
84
|
+
|
85
|
+
y = Y.new
|
86
|
+
y.other_name = Ducktape::BindingSource.new(x, :name, :reverse)
|
87
|
+
|
88
|
+
y.other_name = 'Mary'
|
89
|
+
|
90
|
+
puts x.name
|
91
|
+
puts y.other_name
|
92
|
+
```
|
93
|
+
|
94
|
+
Output:
|
95
|
+
```ruby
|
96
|
+
=> "Mary"
|
97
|
+
=> "Mary"
|
98
|
+
```
|
99
|
+
|
100
|
+
But if you then do:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
x.name = 'John'
|
104
|
+
|
105
|
+
puts x.name
|
106
|
+
puts y.other_name
|
107
|
+
```
|
108
|
+
|
109
|
+
the output will be:
|
110
|
+
```ruby
|
111
|
+
=> "John"
|
112
|
+
=> "Mary"
|
113
|
+
```
|
114
|
+
|
115
|
+
### Removing bindings
|
116
|
+
|
117
|
+
To remove a previously defined binding call the `#unbind_source(attr_name)` method. To remove all bindings for a given object, call the `#clear_bindings` method.
|
118
|
+
|
119
|
+
### Read only / Write only
|
120
|
+
|
121
|
+
BA's can be read-only and write-only. Read-only BA's are useful for reverse only bindings or when the object itself changes the value through the protected method `#set_value`. On the other hand, write-only BA's are best used as sources, though the owner object can call the `#get_value` to get the value of the BA.
|
122
|
+
|
123
|
+
To define a BA as read-only or write-only, use the `:access` modifier.
|
124
|
+
|
125
|
+
Here's an example of read-only and write-only BA's:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class X
|
129
|
+
include Ducktape::Bindable
|
130
|
+
|
131
|
+
bindable :name, access: :readonly
|
132
|
+
|
133
|
+
#no need for this now
|
134
|
+
#
|
135
|
+
#def initialize(name = 'John')
|
136
|
+
# self.name = name
|
137
|
+
#end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Y
|
141
|
+
include Ducktape::Bindable
|
142
|
+
|
143
|
+
bindable :other_name, access: :writeonly
|
144
|
+
end
|
145
|
+
|
146
|
+
x = X.new
|
147
|
+
y = Y.new
|
148
|
+
|
149
|
+
y.other_name = Ducktape::BindingSource.new(x, :name, :reverse)
|
150
|
+
y.other_name = 'Alex'
|
151
|
+
|
152
|
+
puts x.name
|
153
|
+
```
|
154
|
+
|
155
|
+
Output:
|
156
|
+
```ruby
|
157
|
+
=> "Alex"
|
158
|
+
```
|
159
|
+
|
160
|
+
### Default values
|
161
|
+
|
162
|
+
You can set default values for your BA:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
class X
|
166
|
+
include Ducktape::Bindable
|
167
|
+
|
168
|
+
bindable :name, default: 'John'
|
169
|
+
|
170
|
+
#we don't need to do this now:
|
171
|
+
#
|
172
|
+
#def initialize(name = 'John')
|
173
|
+
# self.name = name
|
174
|
+
#end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
### Validation
|
179
|
+
|
180
|
+
You can do validation on a BA.
|
181
|
+
|
182
|
+
Following the last example we could validate `:name` as a String or a Symbol:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
class X
|
186
|
+
include Ducktape::Bindable
|
187
|
+
|
188
|
+
bindable :name, validate: [String, Symbol]
|
189
|
+
|
190
|
+
def initialize(name)
|
191
|
+
self.name = name
|
192
|
+
end
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
Validation works with procs as well. In this case, a proc must have a single parameter which is the new value.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
class X
|
200
|
+
include Ducktape::Bindable
|
201
|
+
|
202
|
+
bindable :name, validate: ->(value){ !value.nil? }
|
203
|
+
|
204
|
+
def initialize(name)
|
205
|
+
self.name = name
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
Validation also works with any kind of objects. For example, to make an enumerable:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
class X
|
214
|
+
include Ducktape::Bindable
|
215
|
+
|
216
|
+
# place attribute will only accept assignment to these three symbols:
|
217
|
+
bindable :place, default: :first, validate: [:first, :middle, :last]
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
In short, you can have a single object or a proc, or an array of objects/procs to validate. If any of them returns "true" (i.e., not `nil` and not `false`), then the new value is accepted. Otherwise it will throw an `InvalidAttributeValueError`.
|
222
|
+
|
223
|
+
### Coercion
|
224
|
+
|
225
|
+
While validation can help knowing when things aren't what we expect, sometimes what we really want is to force a value to remain in a domain. This is where coercion comes in.
|
226
|
+
|
227
|
+
For example, we would like for a float value to remain between 0 and 1, inclusively. If the value goes out of scope, then we want to clamp it to remain between 0 and 1. Additionally, we want other numerical types to be valid, and converted to floats.
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
class X
|
231
|
+
include Ducktape::Bindable
|
232
|
+
|
233
|
+
bindable :my_float,
|
234
|
+
validate: Numeric,
|
235
|
+
default: 0.0,
|
236
|
+
coerce: ->(owner, value) { value = value.to_f; value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value) }
|
237
|
+
end
|
238
|
+
|
239
|
+
x = X.new
|
240
|
+
x.my_float = 2.0
|
241
|
+
|
242
|
+
puts x.my_float
|
243
|
+
```
|
244
|
+
|
245
|
+
The output would be:
|
246
|
+
```ruby
|
247
|
+
=> 1.0
|
248
|
+
```
|
249
|
+
|
250
|
+
Note that if validation is defined, then it will only happen after coercion is applied.
|
251
|
+
|
252
|
+
### Hookable change notifications
|
253
|
+
|
254
|
+
You can watch for changes in a BA by using the public instance method `#on_changed(attr_name, &block)`. Here's an example:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
def attribute_changed(event, owner, attr_name, new_value, old_value)
|
258
|
+
puts "#{owner.class}<#{owner.object_id.to_s(16)}> called the event #{event.inspect} and changed the attribute #{attr_name.inspect} from #{old_value.inspect} to #{new_value.inspect}"
|
259
|
+
end
|
260
|
+
|
261
|
+
class X
|
262
|
+
include Ducktape::Bindable
|
263
|
+
|
264
|
+
bindable :name, validate: [String, Symbol]
|
265
|
+
bindable :age, validate: Integer
|
266
|
+
bindable :points, validate: Integer
|
267
|
+
|
268
|
+
def initialize(name, age, points)
|
269
|
+
self.name = name
|
270
|
+
self.age = age
|
271
|
+
self.points = points
|
272
|
+
|
273
|
+
# You can hook for any method available
|
274
|
+
%w'name age points'.each { |k, v| on_changed k, &method(:attribute_changed) }
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# oops, a misspelling...
|
279
|
+
x = X.new('Richad', 23, 150)
|
280
|
+
|
281
|
+
# It's also useful to see changes outside of the class:
|
282
|
+
x.on_changed 'name', &->(_, _, _, _, new_value) { puts "Hello #{new_value}!" }
|
283
|
+
|
284
|
+
x.name = 'Richard'
|
285
|
+
```
|
286
|
+
|
287
|
+
After calling `#name=`, the output should be something like:
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
=> "Hello Richard!"
|
291
|
+
=> "X<14e35b4> called the event \"on_changed\" and changed the attribute \"name\" from \"Richad\" to \"Richard\""
|
292
|
+
```
|
293
|
+
|
294
|
+
The `on_changed` hook has the following arguments:
|
295
|
+
* the name of the event (in this case, `'on_changed'`)
|
296
|
+
* the caller/owner of the BA (the instance that sent the message),
|
297
|
+
* the name of the BA (`name`, `age`, `points`, etc...),
|
298
|
+
* the new value,
|
299
|
+
* the old value
|
300
|
+
|
301
|
+
Hooks
|
302
|
+
-----
|
303
|
+
|
304
|
+
Has you might have seen, Ducktape comes with hooks, which is what powers the `on_changed` for bindable attributes.
|
305
|
+
You can easily define a hook by using `def_hook`:
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
def called_load(event, owner)
|
309
|
+
puts "#{owner.class}<#{owner.object_id.to_s(16)}> called #{event.inspect}"
|
310
|
+
end
|
311
|
+
|
312
|
+
class X
|
313
|
+
include Ducktape::Hookable
|
314
|
+
|
315
|
+
def_hook :on_loaded #define one or more hooks
|
316
|
+
|
317
|
+
def load
|
318
|
+
call_hooks(:on_loaded)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
x = X.new
|
323
|
+
|
324
|
+
x.on_loaded &method(:called_load)
|
325
|
+
|
326
|
+
#if we didn't create a hook with def_hook we could still use:
|
327
|
+
#x.add_hook :on_loaded, &method(:called_load)
|
328
|
+
|
329
|
+
x.load
|
330
|
+
```
|
331
|
+
|
332
|
+
The output should be something like:
|
333
|
+
```ruby
|
334
|
+
=> "X<14e35b4> called \"on_loaded\""
|
335
|
+
```
|
336
|
+
|
337
|
+
### Removing hooks
|
338
|
+
|
339
|
+
To remove all hooks from an object call the `#clear_hooks` method. To select a single hook, pass the name of the hook as a parameter. The next section has an example of this.
|
340
|
+
|
341
|
+
### Hookable arrays
|
342
|
+
|
343
|
+
A Ducktape::HookableArray is a wrapper for arrays that allows you to add hooks to modifiers of the array.
|
344
|
+
To add a hook to a specific modifier you just have to pass a block to a method that has the same name as the modifier, prefixed with `on_`.
|
345
|
+
|
346
|
+
There are two exceptions to the naming convention:
|
347
|
+
* `HookableArray#[]=`: pass the hook through the `on_assign` method.
|
348
|
+
* `HookableArray#<<`: pass the hook through the `on_append` method.
|
349
|
+
|
350
|
+
The parameters for all these hooks are very similar to the ones used for bindables:
|
351
|
+
* the name of the event (for example, `'on_assign'`)
|
352
|
+
* the instance of `HookableArray` that triggered the hook
|
353
|
+
* an array of the arguments that were passed to the method that triggered the hook (for example, the index and value of the `[]=` method)
|
354
|
+
* and the result of the call to the method
|
355
|
+
|
356
|
+
Additionally, there is a generic `on_changed` hook, that is called for every modifier. In this case, the parametes are:
|
357
|
+
* the name of the event (for example, `'on_assign'`)
|
358
|
+
* the instance of `HookableArray` that triggered the hook
|
359
|
+
* the name of the method (`"[]="`, `"<<"`, "sort!", etc...) that triggered the hook
|
360
|
+
* an array of the arguments that were passed to the method that triggered the hook (for example, the index and value of the `[]=` method)
|
361
|
+
* and the result of the call to the method
|
362
|
+
|
363
|
+
Here is an example that shows how to use the hooks on a HookableArray:
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
a = Ducktape::HookableArray[1, :x] #same as new()
|
367
|
+
a.on_append do |event, owner, args, result|
|
368
|
+
puts "#{event.inspect}, #{owner.class}<#{owner.object_id.to_s(16)}>, #{args.inspect}, #{result.inspect}"
|
369
|
+
end
|
370
|
+
|
371
|
+
a.on_changed do |event, owner, name, args, result|
|
372
|
+
puts "#{event.inspect}, #{owner.class}<#{owner.object_id.to_s(16)}>, #{name.inspect}, #{args.inspect}, #{result.inspect}"
|
373
|
+
end
|
374
|
+
|
375
|
+
a << 'hi'
|
376
|
+
```
|
377
|
+
|
378
|
+
The output would be something like:
|
379
|
+
```ruby
|
380
|
+
=> "\"on_append\", Ducktape::HookableArray<37347c>, [\"hi\"], [1, :x, \"hi\"]"
|
381
|
+
=> "\"on_changed\", Ducktape::HookableArray<37347c>, \"<<\", [\"hi\"], [1, :x, \"hi\"]"
|
382
|
+
```
|
383
|
+
|
384
|
+
If you then do:
|
385
|
+
```ruby
|
386
|
+
a.clear_hooks('on_append')
|
387
|
+
|
388
|
+
a << 'bye'
|
389
|
+
```
|
390
|
+
|
391
|
+
The output will only be for the `on_changed` hook, that wasn't removed:
|
392
|
+
```ruby
|
393
|
+
=> "\"on_changed\", Ducktape::HookableArray<37347c>, \"<<\", [\"bye\"], [1, :x, \"hi\", \"bye\"]"
|
394
|
+
```
|
395
|
+
|
396
|
+
Future work
|
397
|
+
===========
|
398
|
+
* Hashes passed to BA's should check for element changes (hashes with hooks).
|
399
|
+
* Multi-sourced BA's.
|
400
|
+
* More complex binding source paths instead of just the member name (e.g.: ruby like 'a.b.c' or xml like 'a/b/c').
|
data/lib/ducktape/bindable.rb
CHANGED
@@ -1,53 +1,58 @@
|
|
1
1
|
module Ducktape
|
2
2
|
class BindableAttributeMetadata
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
4
|
+
VALID_OPTIONS = [:access, :default, :validate, :coerce].freeze
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name, options = {})
|
9
|
+
|
10
|
+
options.each_key { |k| puts "WARNING: invalid option #{k.inspect} for #{name.inspect} attribute. Will be ignored." unless VALID_OPTIONS.member?(k) }
|
11
|
+
|
12
|
+
if name.is_a? BindableAttributeMetadata
|
13
|
+
@name = name.name
|
14
|
+
@default = options[:default] || name.instance_variable_get(:@default)
|
15
|
+
@validation = options[:validate] || name.instance_variable_get(:@validation)
|
16
|
+
@coercion = options[:coerce] || name.instance_variable_get(:@coercion)
|
17
|
+
else
|
18
|
+
@name = name
|
19
|
+
@default = options[:default]
|
20
|
+
@validation = options[:validate]
|
21
|
+
@coercion = options[:coerce]
|
22
|
+
end
|
23
|
+
|
24
|
+
@validation = [*@validation] unless @validation.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def default=(value)
|
28
|
+
@default = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def default
|
32
|
+
@default.is_a?(Proc) ? @default.call : @default
|
33
|
+
end
|
34
|
+
|
35
|
+
def validation(*options, &block)
|
36
|
+
options << block
|
37
|
+
@validation = options
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate(value)
|
41
|
+
return true unless @validation
|
42
|
+
@validation.each do |validation|
|
43
|
+
return true if (validation.is_a?(Class) and value.is_a?(validation)) or
|
44
|
+
(validation.is_a?(Proc) and validation.call(value)) or
|
45
|
+
value == validation
|
46
|
+
end
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
def coercion(&block)
|
51
|
+
@coercion = block
|
52
|
+
end
|
53
|
+
|
54
|
+
def coerce(owner, value)
|
55
|
+
@coercion ? @coercion.call(owner, value) : value
|
56
|
+
end
|
57
|
+
end
|
53
58
|
end
|
data/lib/ducktape/hookable.rb
CHANGED
@@ -25,6 +25,15 @@ module Ducktape
|
|
25
25
|
self.hooks[event.to_s].delete(block)
|
26
26
|
end
|
27
27
|
|
28
|
+
def clear_hooks(event = nil)
|
29
|
+
if event
|
30
|
+
self.hooks.delete(event.to_s)
|
31
|
+
else
|
32
|
+
self.hooks.clear
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
28
37
|
protected
|
29
38
|
def hooks
|
30
39
|
@hooks ||= Hash.new { |h,k| h[k.to_s] = [] }
|
@@ -1,5 +1,54 @@
|
|
1
1
|
module Ducktape
|
2
|
-
|
2
|
+
class HookableArray
|
3
|
+
include Hookable
|
3
4
|
|
5
|
+
def self.[](*args)
|
6
|
+
new(args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.try_convert(obj)
|
10
|
+
return obj if obj.is_a? self
|
11
|
+
obj = Array.try_convert(obj)
|
12
|
+
return nil if obj.nil?
|
13
|
+
new(obj)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(*args, &block)
|
17
|
+
@array = if args.length == 1 && (args[0].is_a?(Array) || args[0].is_a?(HookableArray))
|
18
|
+
args[0]
|
19
|
+
else
|
20
|
+
Array.new(*args, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(name, *args, &block)
|
25
|
+
@array.public_send(name, *args, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s() @array.to_s end
|
29
|
+
def inspect() @array.inspect end
|
30
|
+
def to_ary() self end
|
31
|
+
|
32
|
+
def_hook 'on_changed'
|
33
|
+
|
34
|
+
compile_hook = ->(name, aka = nil) do
|
35
|
+
aka ||= name
|
36
|
+
aka = "on_#{aka}"
|
37
|
+
|
38
|
+
def_hook(aka) unless method_defined?(aka)
|
39
|
+
|
40
|
+
define_method(name) do |*args, &block|
|
41
|
+
result = @array.public_send(__method__, *args, &block)
|
42
|
+
call_hooks(aka, self, args, result)
|
43
|
+
call_hooks('on_changed', self, name, args, result)
|
44
|
+
result
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
%w'clear collect! compact! concat delete delete_at delete_if fill flatten! insert keep_if
|
49
|
+
map! pop push reject! replace reverse! rotate! select! shift shuffle! slice! sort!
|
50
|
+
sort_by! uniq! unshift'.each { |m| compile_hook.(m) }
|
51
|
+
|
52
|
+
{ '[]=' => 'assign', '<<' => 'append' }.each { |k, v| compile_hook.(k, v) }
|
4
53
|
end
|
5
54
|
end
|
data/lib/ducktape.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
|
2
|
-
ROOT = File.expand_path('../ducktape', __FILE__)
|
1
|
+
require 'ducktape/version'
|
3
2
|
|
3
|
+
module Ducktape
|
4
4
|
{
|
5
5
|
:Bindable => 'bindable',
|
6
6
|
:BindableAttribute => 'bindable_attribute',
|
7
7
|
:BindableAttributeMetadata => 'bindable_attribute_metadata',
|
8
8
|
:BindingSource => 'binding_source',
|
9
|
-
:Hookable => 'hookable'
|
10
|
-
|
9
|
+
:Hookable => 'hookable',
|
10
|
+
:HookableArray => 'hookable_array'
|
11
|
+
}.each { |k, v| autoload k, "ducktape/#{v}" }
|
11
12
|
end
|
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.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-05-01 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: Truly outrageous bindable attributes
|
16
16
|
email:
|
@@ -20,13 +20,15 @@ executables: []
|
|
20
20
|
extensions: []
|
21
21
|
extra_rdoc_files: []
|
22
22
|
files:
|
23
|
-
- lib/ducktape.rb
|
24
23
|
- lib/ducktape/bindable.rb
|
25
24
|
- lib/ducktape/bindable_attribute.rb
|
26
25
|
- lib/ducktape/bindable_attribute_metadata.rb
|
27
26
|
- lib/ducktape/binding_source.rb
|
28
27
|
- lib/ducktape/hookable.rb
|
29
28
|
- lib/ducktape/hookable_array.rb
|
29
|
+
- lib/ducktape/version.rb
|
30
|
+
- lib/ducktape.rb
|
31
|
+
- README.md
|
30
32
|
homepage: https://github.com/SilverPhoenix99/ducktape
|
31
33
|
licenses: []
|
32
34
|
post_install_message:
|