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.
- checksums.yaml +4 -4
- data/Gemfile.lock +6 -6
- data/collapsium.gemspec +1 -1
- data/lib/collapsium.rb +6 -1
- data/lib/collapsium/environment_override.rb +167 -0
- data/lib/collapsium/pathed_access.rb +173 -81
- data/lib/collapsium/recursive_dup.rb +8 -3
- data/lib/collapsium/recursive_merge.rb +8 -4
- data/lib/collapsium/recursive_sort.rb +6 -33
- data/lib/collapsium/support/hash_methods.rb +48 -0
- data/lib/collapsium/support/methods.rb +88 -0
- data/lib/collapsium/uber_hash.rb +7 -1
- data/lib/collapsium/version.rb +1 -1
- data/lib/collapsium/viral_capabilities.rb +151 -0
- data/spec/environment_override_spec.rb +154 -0
- data/spec/pathed_access_spec.rb +83 -0
- data/spec/recursive_merge_spec.rb +17 -0
- data/spec/support_methods_spec.rb +231 -0
- data/spec/uber_hash_spec.rb +16 -0
- data/spec/viral_capabilities_spec.rb +248 -0
- metadata +14 -5
@@ -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 =
|
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
|
-
|
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
|
-
|
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
|
data/lib/collapsium/uber_hash.rb
CHANGED
@@ -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
|
-
|
44
|
+
recursive_merge!(args[0])
|
39
45
|
end
|
40
46
|
end
|
41
47
|
|
data/lib/collapsium/version.rb
CHANGED
@@ -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
|