collapsium 0.3.0 → 0.4.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.
@@ -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