ducktape 0.1.0 → 0.2.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 +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