ducktape 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -12
- data/lib/ducktape/hookable_array.rb +49 -34
- data/lib/ducktape/hookable_collection.rb +58 -0
- data/lib/ducktape/hookable_hash.rb +55 -0
- data/lib/ducktape.rb +15 -9
- data/lib/version.rb +3 -0
- metadata +5 -3
- data/lib/ducktape/version.rb +0 -3
data/README.md
CHANGED
@@ -338,29 +338,30 @@ The output should be something like:
|
|
338
338
|
|
339
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
340
|
|
341
|
-
### Hookable arrays
|
341
|
+
### Hookable arrays and hashes
|
342
342
|
|
343
|
-
A Ducktape::HookableArray is a wrapper for arrays that allows you to add hooks to modifiers of the array.
|
343
|
+
A `Ducktape::HookableArray` is a wrapper for arrays that allows you to add hooks to modifiers of the array. The same concept applies to `Ducktape::HookableHash` in relation to hashes.
|
344
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
345
|
|
346
|
-
There are
|
347
|
-
* `HookableArray#[]=`: pass the hook through the `on_assign` method.
|
346
|
+
There are three exceptions to the naming convention:
|
348
347
|
* `HookableArray#<<`: pass the hook through the `on_append` method.
|
348
|
+
* `HookableArray#[]=`: pass the hook through the `on_store` method.
|
349
|
+
* `HookableHash#[]=`: pass the hook through the `on_store` method.
|
349
350
|
|
350
|
-
The parameters for all
|
351
|
-
* the name of the event (for example, `'
|
352
|
-
* the instance of `HookableArray` that triggered the hook
|
351
|
+
The parameters for all the hooks are very similar to the ones used for bindables:
|
352
|
+
* the name of the event (for example, `'on_store'`)
|
353
|
+
* the instance of `HookableArray` or `HookableHash` that triggered the hook
|
353
354
|
* 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
355
|
* and the result of the call to the method
|
355
356
|
|
356
357
|
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, `'
|
358
|
-
* the instance of `HookableArray` that triggered the hook
|
358
|
+
* the name of the event (for example, `'on_store'`)
|
359
|
+
* the instance of `HookableArray` or `HookableHash` that triggered the hook
|
359
360
|
* the name of the method (`"[]="`, `"<<"`, "sort!", etc...) that triggered the hook
|
360
361
|
* 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
362
|
* and the result of the call to the method
|
362
363
|
|
363
|
-
Here is an example that shows how to use the hooks on a HookableArray
|
364
|
+
Here is an example that shows how to use the hooks on a `HookableArray`:
|
364
365
|
|
365
366
|
```ruby
|
366
367
|
a = Ducktape::HookableArray[1, :x] #same as new()
|
@@ -388,13 +389,13 @@ a.clear_hooks('on_append')
|
|
388
389
|
a << 'bye'
|
389
390
|
```
|
390
391
|
|
391
|
-
The output will only be for the `on_changed` hook,
|
392
|
+
The output will only be for the `on_changed` hook, which wasn't removed:
|
392
393
|
```ruby
|
393
394
|
=> "\"on_changed\", Ducktape::HookableArray<37347c>, \"<<\", [\"bye\"], [1, :x, \"hi\", \"bye\"]"
|
394
395
|
```
|
395
396
|
|
396
397
|
Future work
|
397
398
|
===========
|
398
|
-
*
|
399
|
+
* Pass a hook by method name. This will provide a more dynamic binding if the method is overriden.
|
399
400
|
* Multi-sourced BA's.
|
400
401
|
* More complex binding source paths instead of just the member name (e.g.: ruby like 'a.b.c' or xml like 'a/b/c').
|
@@ -1,54 +1,69 @@
|
|
1
1
|
module Ducktape
|
2
2
|
class HookableArray
|
3
|
-
include
|
3
|
+
include HookableCollection
|
4
4
|
|
5
5
|
def self.[](*args)
|
6
|
-
new(args)
|
6
|
+
new([*args])
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.try_convert(obj)
|
10
10
|
return obj if obj.is_a? self
|
11
11
|
obj = Array.try_convert(obj)
|
12
|
-
|
13
|
-
new(obj)
|
12
|
+
obj ? new(obj) : nil
|
14
13
|
end
|
15
14
|
|
15
|
+
# Careful when duping arrays. Duping is shallow.
|
16
16
|
def initialize(*args, &block)
|
17
|
-
@
|
18
|
-
args[0]
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
@content = if args.length == 1
|
18
|
+
arg = args[0]
|
19
|
+
case
|
20
|
+
when arg.is_a?(Array) then arg.dup
|
21
|
+
when arg.is_a?(HookableArray) then arg.instance_variable_get('@content').dup
|
22
|
+
when arg.is_a?(Enumerable) || arg.respond_to?(:to_a) then arg.to_a
|
23
|
+
end
|
24
|
+
end || Array.new(*args, &block)
|
22
25
|
end
|
23
26
|
|
24
|
-
def
|
25
|
-
@array.public_send(name, *args, &block)
|
26
|
-
end
|
27
|
-
|
28
|
-
def to_s() @array.to_s end
|
29
|
-
def inspect() @array.inspect end
|
27
|
+
def to_a() self end
|
30
28
|
def to_ary() self end
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
30
|
+
def ==(other)
|
31
|
+
other = Array.try_convert(other)
|
32
|
+
return false unless other || other.count != self.count
|
33
|
+
enum = other.each
|
34
|
+
each { |v1| return false unless v1 == enum.next }
|
35
|
+
true
|
46
36
|
end
|
47
37
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
38
|
+
compile_hooks(
|
39
|
+
%w'clear
|
40
|
+
collect!
|
41
|
+
compact!
|
42
|
+
concat
|
43
|
+
delete
|
44
|
+
delete_at
|
45
|
+
delete_if
|
46
|
+
fill
|
47
|
+
flatten!
|
48
|
+
insert
|
49
|
+
keep_if
|
50
|
+
map!
|
51
|
+
pop
|
52
|
+
push
|
53
|
+
reject!
|
54
|
+
replace
|
55
|
+
reverse!
|
56
|
+
rotate!
|
57
|
+
select!
|
58
|
+
shift
|
59
|
+
shuffle!
|
60
|
+
slice!
|
61
|
+
sort!
|
62
|
+
sort_by!
|
63
|
+
uniq!
|
64
|
+
unshift',
|
65
|
+
'<<' => 'append',
|
66
|
+
'[]=' => 'store'
|
67
|
+
)
|
53
68
|
end
|
54
69
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Ducktape
|
2
|
+
module HookableCollection
|
3
|
+
extend Hookable::ClassMethods
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def compile_hooks(names_ary, names_hash = {})
|
7
|
+
#Reversed merge because names_hash has priority.
|
8
|
+
names_hash = Hash[names_ary.map { |v| [v, v] }].merge!(names_hash)
|
9
|
+
|
10
|
+
names_hash.each do |name, aka|
|
11
|
+
aka = "on_#{aka}"
|
12
|
+
def_hook(aka) unless method_defined?(aka)
|
13
|
+
defined_hooks[name.to_s] = aka
|
14
|
+
end
|
15
|
+
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def defined_hooks
|
20
|
+
@defined_hooks ||= {}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.included(base)
|
25
|
+
base.send(:include, Hookable)
|
26
|
+
base.extend(ClassMethods)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.extended(_)
|
30
|
+
raise 'Cannot extend, only include.'
|
31
|
+
end
|
32
|
+
|
33
|
+
def_hook 'on_changed'
|
34
|
+
|
35
|
+
def content() @content.dup end
|
36
|
+
def to_s() "#{self.class}#{@content.to_s}" end
|
37
|
+
def inspect() "#{self.class}#{@content.inspect}" end
|
38
|
+
def hash() @content.hash end
|
39
|
+
def dup() self.class.new(@content) end
|
40
|
+
|
41
|
+
def eq?(other)
|
42
|
+
equal?(other) || self == other
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_missing(name, *args, &block)
|
46
|
+
result = @content.public_send(name, *args, &block)
|
47
|
+
result = self if result.equal?(@content)
|
48
|
+
|
49
|
+
aka = self.class.defined_hooks[name.to_s]
|
50
|
+
if aka
|
51
|
+
call_hooks(aka, self, args, result)
|
52
|
+
call_hooks('on_changed', self, name, args, result)
|
53
|
+
end
|
54
|
+
|
55
|
+
result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Ducktape
|
2
|
+
class HookableHash
|
3
|
+
include HookableCollection
|
4
|
+
|
5
|
+
def self.[](*args)
|
6
|
+
new(Hash[*args])
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.try_convert(obj)
|
10
|
+
return obj if obj.is_a? self
|
11
|
+
obj = Hash.try_convert(obj)
|
12
|
+
obj ? new(obj) : nil
|
13
|
+
end
|
14
|
+
|
15
|
+
# Careful with duping hashes. Duping is shallow.
|
16
|
+
def initialize(*args, &block)
|
17
|
+
@content = if args.length == 1
|
18
|
+
arg = args[0]
|
19
|
+
case
|
20
|
+
when arg.is_a?(Hash) then arg.dup
|
21
|
+
when arg.is_a?(HookableHash) then arg.instance_variable_get('@hash').dup
|
22
|
+
when arg.respond_to?(:to_hash) then arg.to_hash
|
23
|
+
end
|
24
|
+
end || Hash.new(*args, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash() self end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
other = Hash.try_convert(other)
|
31
|
+
return false unless other || other.count != self.count
|
32
|
+
enum = other.each
|
33
|
+
each { |v1| return false unless v1 == enum.next }
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
compile_hooks(
|
38
|
+
%w'clear
|
39
|
+
default=
|
40
|
+
default_proc=
|
41
|
+
delete
|
42
|
+
delete_if
|
43
|
+
keep_if
|
44
|
+
merge!
|
45
|
+
rehash
|
46
|
+
reject!
|
47
|
+
replace
|
48
|
+
select!
|
49
|
+
shift
|
50
|
+
store
|
51
|
+
update',
|
52
|
+
'[]=' => 'store'
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
data/lib/ducktape.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
-
require '
|
1
|
+
require 'version'
|
2
2
|
|
3
3
|
module Ducktape
|
4
|
-
{
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
camelize = ->(f){ f.gsub(/(^|_)([^_]+)/) { |_| $2.capitalize } }
|
5
|
+
|
6
|
+
Dir["#{File.expand_path('../ducktape', __FILE__)}/*.rb"].
|
7
|
+
map { |f| File.basename(f, File.extname(f)) }.
|
8
|
+
each { |f| autoload camelize.(f), "ducktape/#{f}" }
|
9
|
+
|
10
|
+
#%w'bindable
|
11
|
+
# bindable_attribute
|
12
|
+
# bindable_attribute_metadata
|
13
|
+
# binding_source
|
14
|
+
# hookable
|
15
|
+
# hookable_array
|
16
|
+
# hookable_collection
|
17
|
+
# hookable_hash'.each { |f| autoload camelize.(f), "ducktape/#{f}" }
|
12
18
|
end
|
data/lib/version.rb
ADDED
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.2.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-05-
|
13
|
+
date: 2012-05-20 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: Truly outrageous bindable attributes
|
16
16
|
email:
|
@@ -26,8 +26,10 @@ files:
|
|
26
26
|
- lib/ducktape/binding_source.rb
|
27
27
|
- lib/ducktape/hookable.rb
|
28
28
|
- lib/ducktape/hookable_array.rb
|
29
|
-
- lib/ducktape/
|
29
|
+
- lib/ducktape/hookable_collection.rb
|
30
|
+
- lib/ducktape/hookable_hash.rb
|
30
31
|
- lib/ducktape.rb
|
32
|
+
- lib/version.rb
|
31
33
|
- README.md
|
32
34
|
homepage: https://github.com/SilverPhoenix99/ducktape
|
33
35
|
licenses: []
|
data/lib/ducktape/version.rb
DELETED