ducktape 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|