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 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 two exceptions to the naming convention:
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 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
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, `'on_assign'`)
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, that wasn't removed:
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
- * Hashes passed to BA's should check for element changes (hashes with hooks).
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 Hookable
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
- return nil if obj.nil?
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
- @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
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 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
27
+ def to_a() self end
30
28
  def to_ary() self end
31
29
 
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
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
- %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) }
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 'ducktape/version'
1
+ require 'version'
2
2
 
3
3
  module Ducktape
4
- {
5
- :Bindable => 'bindable',
6
- :BindableAttribute => 'bindable_attribute',
7
- :BindableAttributeMetadata => 'bindable_attribute_metadata',
8
- :BindingSource => 'binding_source',
9
- :Hookable => 'hookable',
10
- :HookableArray => 'hookable_array'
11
- }.each { |k, v| autoload k, "ducktape/#{v}" }
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
@@ -0,0 +1,3 @@
1
+ module Ducktape
2
+ VERSION = '0.2.0'
3
+ 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.1.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-01 00:00:00.000000000 Z
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/version.rb
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: []
@@ -1,3 +0,0 @@
1
- module Ducktape
2
- VERSION = '0.1.0'
3
- end