collapsium 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,16 +7,20 @@
7
7
  # All rights reserved.
8
8
  #
9
9
 
10
+ require 'collapsium/viral_capabilities'
11
+
10
12
  module Collapsium
11
13
  ##
12
14
  # Provides recursive (deep) dup function for hashes.
13
15
  module RecursiveDup
16
+ # Clone Hash extensions for nested Hashes
17
+ extend ViralCapabilities
18
+
14
19
  def recursive_dup
15
20
  ret = map do |k, v|
16
21
  # Hash values, recurse into them.
17
22
  if v.is_a?(Hash)
18
- n = v.dup # we duplicate v to not extend it.
19
- n.extend(RecursiveDup)
23
+ n = ViralCapabilities.enhance_hash_value(self, v)
20
24
  next [k, n.recursive_dup]
21
25
  end
22
26
 
@@ -28,7 +32,8 @@ module Collapsium
28
32
  next [k, v]
29
33
  end
30
34
  end
31
- return Hash[ret]
35
+ ret = Hash[ret]
36
+ return ViralCapabilities.enhance_hash_value(self, ret)
32
37
  end
33
38
 
34
39
  alias deep_dup recursive_dup
@@ -7,10 +7,16 @@
7
7
  # All rights reserved.
8
8
  #
9
9
 
10
+ require 'collapsium/viral_capabilities'
11
+
10
12
  module Collapsium
11
13
  ##
12
14
  # Provides recursive merge functions for hashes.
13
15
  module RecursiveMerge
16
+
17
+ # Make the capabilities of classes using RecursiveMerge viral.
18
+ extend ViralCapabilities
19
+
14
20
  ##
15
21
  # Recursively merge `:other` into this Hash.
16
22
  #
@@ -31,7 +37,7 @@ module Collapsium
31
37
  merger = proc do |_, v1, v2|
32
38
  # rubocop:disable Style/GuardClause
33
39
  if v1.is_a? Hash and v2.is_a? Hash
34
- next v1.merge(v2, &merger)
40
+ next v1.merge!(v2, &merger)
35
41
  elsif v1.is_a? Array and v2.is_a? Array
36
42
  next v1 + v2
37
43
  end
@@ -49,9 +55,7 @@ module Collapsium
49
55
  # Same as `dup.recursive_merge!`
50
56
  # @param (see #recursive_merge!)
51
57
  def recursive_merge(other, overwrite = true)
52
- copy = dup
53
- copy.extend(RecursiveMerge)
54
- return copy.recursive_merge!(other, overwrite)
58
+ return dup.recursive_merge!(other, overwrite)
55
59
  end
56
60
  end # module RecursiveMerge
57
61
  end # module Collapsium
@@ -7,10 +7,16 @@
7
7
  # All rights reserved.
8
8
  #
9
9
 
10
+ require 'collapsium/viral_capabilities'
11
+
10
12
  module Collapsium
11
13
  ##
12
14
  # Provides recursive sort functions for hashes.
13
15
  module RecursiveSort
16
+ # Virality means we don't have to rextend with RecursiveSort to sort
17
+ # nested values recursively.
18
+ extend ViralCapabilities
19
+
14
20
  ##
15
21
  # Recursively sort a Hash by its keys. Without a block, this function will
16
22
  # not be able to compare keys of different size.
@@ -22,7 +28,6 @@ module Collapsium
22
28
 
23
29
  # Recurse into Hash values
24
30
  if value.is_a?(Hash)
25
- value.extend(RecursiveSort)
26
31
  value.recursive_sort!(&block)
27
32
  end
28
33
 
@@ -42,39 +47,7 @@ module Collapsium
42
47
  else
43
48
  ret = dup
44
49
  end
45
- ret.extend(RecursiveSort)
46
50
  return ret.recursive_sort!(&block)
47
51
  end
48
-
49
- # def recursive_merge!(other, overwrite = true)
50
- # if other.nil?
51
- # return self
52
- # end
53
- #
54
- # merger = proc do |_, v1, v2|
55
- # # rubocop:disable Style/GuardClause
56
- # if v1.is_a? Hash and v2.is_a? Hash
57
- # next v1.merge(v2, &merger)
58
- # elsif v1.is_a? Array and v2.is_a? Array
59
- # next v1 + v2
60
- # end
61
- # if overwrite
62
- # next v2
63
- # else
64
- # next v1
65
- # end
66
- # # rubocop:enable Style/GuardClause
67
- # end
68
- # merge!(other, &merger)
69
- # end
70
- #
71
- # ##
72
- # # Same as `dup.recursive_merge!`
73
- # # @param (see #recursive_merge!)
74
- # def recursive_merge(other, overwrite = true)
75
- # copy = dup
76
- # copy.extend(RecursiveMerge)
77
- # return copy.recursive_merge!(other, overwrite)
78
- # end
79
52
  end # module RecursiveSort
80
53
  end # module Collapsium
@@ -0,0 +1,48 @@
1
+ # coding: utf-8
2
+ #
3
+ # collapsium
4
+ # https://github.com/jfinkhaeuser/collapsium
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other collapsium contributors.
7
+ # All rights reserved.
8
+ #
9
+
10
+ module Collapsium
11
+
12
+ ##
13
+ # Support functionality for Collapsium
14
+ module Support
15
+
16
+ ##
17
+ # @api private
18
+ # Defines which read and write functions we expect Hash to have.
19
+ module HashMethods
20
+
21
+ # @api private
22
+ # Read access methods with key parameter
23
+ KEYED_READ_METHODS = [
24
+ :[], :default, :fetch, :has_key?, :include?, :key?,
25
+ ].freeze
26
+
27
+ # @api private
28
+ # All read access methods
29
+ READ_METHODS = KEYED_READ_METHODS + [
30
+ :dup,
31
+ ].freeze
32
+
33
+ # @api private
34
+ # Write access methods with key parameter
35
+ KEYED_WRITE_METHODS = [
36
+ :[]=, :delete, :store,
37
+ ].freeze
38
+
39
+ # All write access methods
40
+ WRITE_METHODS = KEYED_WRITE_METHODS + [
41
+ :merge, :merge!,
42
+ ].freeze
43
+
44
+ end # module HashMethods
45
+
46
+ end # module Support
47
+
48
+ end # module Collapsium
@@ -0,0 +1,88 @@
1
+ # coding: utf-8
2
+ #
3
+ # collapsium
4
+ # https://github.com/jfinkhaeuser/collapsium
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other collapsium contributors.
7
+ # All rights reserved.
8
+ #
9
+
10
+ module Collapsium
11
+
12
+ ##
13
+ # Support functionality for Collapsium
14
+ module Support
15
+
16
+ ##
17
+ # Functionality for extending the behaviour of Hash methods
18
+ module Methods
19
+
20
+ ##
21
+ # Given the base module, wraps the given method name in the given block.
22
+ # The block must accept the super_method as the first parameter, followed
23
+ # by any arguments and blocks the super method might accept.
24
+ #
25
+ # The canonical usage example is of a module that when prepended wraps
26
+ # some methods with extra functionality:
27
+ #
28
+ # ```ruby
29
+ # module MyModule
30
+ # class << self
31
+ # include ::Collapsium::Support::Methods
32
+ #
33
+ # def prepended(base)
34
+ # wrap_method(base, :method_to_wrap) do |super_method, *args, &block|
35
+ # # modify args, if desired
36
+ # result = super_method.call(*args, &block)
37
+ # # do something with the result, if desired
38
+ # next result
39
+ # end
40
+ # end
41
+ # end
42
+ # end
43
+ # ```
44
+ def wrap_method(base, method_name, raise_on_missing = true, &wrapper_block)
45
+ # The base class must define an instance method of method_name, otherwise
46
+ # this will NameError. That's also a good check that sensible things are
47
+ # being done.
48
+ base_method = nil
49
+ def_method = nil
50
+ if base.is_a? Module
51
+ # Modules *may* not be fully defined when this is called, so in some
52
+ # cases it's best to ignore NameErrors.
53
+ begin
54
+ base_method = base.instance_method(method_name.to_sym)
55
+ rescue NameError
56
+ if raise_on_missing
57
+ raise
58
+ end
59
+ return
60
+ end
61
+ def_method = base.method(:define_method)
62
+ else
63
+ # For Objects and Classes, the unbound method will later be bound to
64
+ # the object or class to define the method on.
65
+ base_method = base.method(method_name.to_s).unbind
66
+ # With regards to method defintion, we only want to define methods
67
+ # for the specific instance (i.e. use :define_singleton_method).
68
+ def_method = base.method(:define_singleton_method)
69
+ end
70
+
71
+ # Hack for calling the private method "define_method"
72
+ def_method.call(method_name) do |*args, &method_block|
73
+ # Prepend the old method to the argument list; but bind it to the current
74
+ # instance.
75
+ super_method = base_method.bind(self)
76
+ args.unshift(super_method)
77
+
78
+ # Then yield to the given wrapper block. The wrapper should decide
79
+ # whether to call the old method or not.
80
+ next wrapper_block.call(*args, &method_block)
81
+ end
82
+ end
83
+
84
+ end # module Methods
85
+
86
+ end # module Support
87
+
88
+ end # module Collapsium
@@ -13,17 +13,23 @@ require 'collapsium/recursive_sort'
13
13
  require 'collapsium/indifferent_access'
14
14
  require 'collapsium/pathed_access'
15
15
  require 'collapsium/prototype_match'
16
+ require 'collapsium/viral_capabilities'
17
+
18
+ require 'collapsium/support/hash_methods'
16
19
 
17
20
  module Collapsium
18
21
 
19
22
  # A Hash that includes all the different Hash extensions in collapsium
20
23
  class UberHash < Hash
24
+ include ViralCapabilities
21
25
  include RecursiveMerge
22
26
  include RecursiveDup
23
27
  include RecursiveSort
24
28
  include PathedAccess
25
29
  include PrototypeMatch
26
30
 
31
+ include Support::HashMethods
32
+
27
33
  def initialize(*args)
28
34
  super
29
35
 
@@ -35,7 +41,7 @@ module Collapsium
35
41
  return
36
42
  end
37
43
 
38
- merge!(args[0])
44
+ recursive_merge!(args[0])
39
45
  end
40
46
  end
41
47
 
@@ -8,5 +8,5 @@
8
8
  #
9
9
  module Collapsium
10
10
  # The current release version
11
- VERSION = "0.3.0".freeze
11
+ VERSION = "0.4.0".freeze
12
12
  end
@@ -0,0 +1,151 @@
1
+ # coding: utf-8
2
+ #
3
+ # collapsium
4
+ # https://github.com/jfinkhaeuser/collapsium
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other collapsium contributors.
7
+ # All rights reserved.
8
+ #
9
+
10
+ require 'collapsium/support/hash_methods'
11
+ require 'collapsium/support/methods'
12
+
13
+ module Collapsium
14
+ ##
15
+ # Tries to make extended Hash capabilities viral, i.e. provides the same
16
+ # features to nested Hash structures as the Hash that includes this module.
17
+ #
18
+ # Virality is ensured by changing the return value of various methods; if it
19
+ # is derived from Hash, it is attempted to convert it to the including class.
20
+ #
21
+ # The module uses HashMethods to decide which methods to make viral in this
22
+ # manner.
23
+ #
24
+ # There are two ways for using this module:
25
+ # a) in a `Class`, either include, prepend or extend it.
26
+ # b) in a `Module`, *extend* this module. The resulting module can be included,
27
+ # prepended or extended in a `Class` again.
28
+ module ViralCapabilities
29
+
30
+ include ::Collapsium::Support::HashMethods
31
+ include ::Collapsium::Support::Methods
32
+
33
+ ##
34
+ # When prepended, included or extended, enhance the base.
35
+ def prepended(base)
36
+ ViralCapabilities.enhance(base)
37
+ end
38
+
39
+ def included(base)
40
+ ViralCapabilities.enhance(base)
41
+ end
42
+
43
+ def extended(base)
44
+ ViralCapabilities.enhance(base)
45
+ end
46
+
47
+ class << self
48
+ include ::Collapsium::Support::HashMethods
49
+ include ::Collapsium::Support::Methods
50
+
51
+ ##
52
+ # When prepended, included or extended, enhance the base.
53
+ def prepended(base)
54
+ enhance(base)
55
+ end
56
+
57
+ def included(base)
58
+ enhance(base)
59
+ end
60
+
61
+ def extended(base)
62
+ enhance(base)
63
+ end
64
+
65
+ ##
66
+ # Enhance the base by wrapping all READ_METHODS and WRITE_METHODS in
67
+ # a wrapper that uses enhance_hash_value to, well, enhance Hash results.
68
+ def enhance(base)
69
+ # rubocop:disable Style/ClassVars
70
+ @@write_block ||= proc do |super_method, *args, &block|
71
+ arg_copy = args.map do |arg|
72
+ enhance_hash_value(super_method.receiver, arg)
73
+ end
74
+ result = super_method.call(*arg_copy, &block)
75
+ next enhance_hash_value(super_method.receiver, result)
76
+ end
77
+ @@read_block ||= proc do |super_method, *args, &block|
78
+ result = super_method.call(*args, &block)
79
+ next enhance_hash_value(super_method.receiver, result)
80
+ end
81
+ # rubocop:enable Style/ClassVars
82
+
83
+ READ_METHODS.each do |method_name|
84
+ wrap_method(base, method_name, false, &@@read_block)
85
+ end
86
+
87
+ WRITE_METHODS.each do |method_name|
88
+ wrap_method(base, method_name, false, &@@write_block)
89
+ end
90
+ end
91
+
92
+ ##
93
+ # Given an outer Hash and a value, enhance Hash values so that they have
94
+ # the same capabilities as the outer Hash. Non-Hash values are returned
95
+ # unchanged.
96
+ def enhance_hash_value(outer_hash, value)
97
+ # If the value is not a Hash, we don't do anything.
98
+ if not value.is_a? Hash
99
+ return value
100
+ end
101
+
102
+ # If the value is a different type of Hash from ourself, we want to
103
+ # create an instance of our own type with the same values.
104
+ # XXX: DO NOT replace the loop with :merge! or :merge - those are
105
+ # potentially wrapped write functions, leading to an infinite
106
+ # recursion.
107
+ if value.class != outer_hash.class
108
+ new_value = outer_hash.class.new
109
+
110
+ value.each do |key, inner_val|
111
+ if not inner_val.is_a? Hash
112
+ new_value[key] = inner_val
113
+ next
114
+ end
115
+
116
+ if inner_val.class == outer_hash.class
117
+ new_value[key] = inner_val
118
+ next
119
+ end
120
+
121
+ new_inner_value = outer_hash.class.new
122
+ new_inner_value.merge!(inner_val)
123
+ new_value[key] = new_inner_value
124
+ end
125
+ value = new_value
126
+ end
127
+
128
+ # Next, we want to extend all the modules in self. That might be a
129
+ # no-op due to the above block, but not necessarily so.
130
+ value_mods = (class << value; self end).included_modules
131
+ own_mods = (class << outer_hash; self end).included_modules
132
+ (own_mods - value_mods).each do |mod|
133
+ value.extend(mod)
134
+ end
135
+
136
+ # If we have a default_proc and the value doesn't, we want to use our
137
+ # own. This *can* override a perfectly fine default_proc with our own,
138
+ # which might suck.
139
+ value.default_proc ||= outer_hash.default_proc
140
+
141
+ # Finally, the class can define its own virality function.
142
+ if outer_hash.respond_to?(:virality)
143
+ value = outer_hash.virality(value)
144
+ end
145
+
146
+ return value
147
+ end
148
+ end
149
+
150
+ end # module ViralCapabilities
151
+ end # module Collapsium