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 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