collapsium 0.6.1 → 0.7.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/.travis.yml +3 -0
- data/Gemfile.lock +4 -4
- data/lib/collapsium/indifferent_access.rb +109 -11
- data/lib/collapsium/pathed_access.rb +3 -2
- data/lib/collapsium/recursive_merge.rb +46 -15
- data/lib/collapsium/recursive_sort.rb +10 -1
- data/lib/collapsium/support/methods.rb +65 -7
- data/lib/collapsium/uber_hash.rb +1 -3
- data/lib/collapsium/version.rb +1 -1
- data/lib/collapsium/viral_capabilities.rb +9 -15
- data/spec/indifferent_access_spec.rb +106 -11
- data/spec/pathed_access_spec.rb +104 -48
- data/spec/recursive_merge_spec.rb +117 -0
- data/spec/recursive_sort_spec.rb +14 -5
- data/spec/spec_helper.rb +0 -6
- data/spec/{support_path_components.rb → support_path_components_spec.rb} +0 -0
- data/spec/uber_hash_spec.rb +5 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0237b4fe627fff71b5c02f5e3f77f9d9713e849
|
4
|
+
data.tar.gz: fd48fd69e118edfba70a91ee7920495008a140ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69e87ad355d708944ef6bda61139a63028a666cf9165b2438c2b47da3edc92fdaa4b581435528ef31754b09cd265e2908dc3205c049e9ea1424b4d7b93536cfe
|
7
|
+
data.tar.gz: f85612ff08aeaf56ca7699a68ef171709905aa838eabf26c7196002b95529cb79eeff998a5a546a70cf46d64485842cc913449e4d073a51e441ba1d50102dec2
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
collapsium (0.
|
4
|
+
collapsium (0.7.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
ast (2.3.0)
|
10
|
-
codeclimate-test-reporter (0.
|
11
|
-
simplecov
|
10
|
+
codeclimate-test-reporter (1.0.3)
|
11
|
+
simplecov
|
12
12
|
diff-lcs (1.2.5)
|
13
13
|
docile (1.1.5)
|
14
14
|
json (2.0.2)
|
15
|
-
parser (2.3.
|
15
|
+
parser (2.3.2.0)
|
16
16
|
ast (~> 2.2)
|
17
17
|
powerpack (0.1.1)
|
18
18
|
rainbow (2.1.0)
|
@@ -7,6 +7,11 @@
|
|
7
7
|
# All rights reserved.
|
8
8
|
#
|
9
9
|
|
10
|
+
require 'collapsium/support/hash_methods'
|
11
|
+
require 'collapsium/support/methods'
|
12
|
+
|
13
|
+
require 'collapsium/viral_capabilities'
|
14
|
+
|
10
15
|
module Collapsium
|
11
16
|
|
12
17
|
##
|
@@ -16,18 +21,111 @@ module Collapsium
|
|
16
21
|
module IndifferentAccess
|
17
22
|
|
18
23
|
##
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
# If the module is included, extended or prepended in a class, it'll
|
25
|
+
# wrap accessor methods.
|
26
|
+
class << self
|
27
|
+
include ::Collapsium::Support::Methods
|
28
|
+
|
29
|
+
##
|
30
|
+
# Given a key, returns all indifferent permutations to try.
|
31
|
+
def key_permutations(key)
|
32
|
+
tries = [key]
|
33
|
+
if key.is_a? Symbol
|
34
|
+
key_s = key.to_s
|
35
|
+
tries << key_s
|
36
|
+
if key_s =~ /^[0-9]/
|
37
|
+
tries << key_s.to_i
|
38
|
+
end
|
39
|
+
elsif key.is_a? String
|
40
|
+
tries << key.to_sym
|
41
|
+
if key =~ /^[0-9]/
|
42
|
+
tries << key.to_i
|
43
|
+
end
|
44
|
+
elsif key.is_a? Integer
|
45
|
+
tries += [key.to_s, key.to_s.to_sym]
|
46
|
+
end
|
47
|
+
|
48
|
+
return tries
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Make the given keys unique according to the logic of this module.
|
53
|
+
def unique_keys(keys)
|
54
|
+
# The simplest way is to stringify all keys before making them
|
55
|
+
# unique. That works for Integer as well as Symbol.
|
56
|
+
return keys.map(&:to_s).uniq
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Sort the given keys indifferently. This will sort Integers first,
|
61
|
+
# Symbols second and Strings third. Everything else comes last. This
|
62
|
+
# is done because that's the order in which comparsion time increases.
|
63
|
+
def sorted_keys(keys, &block)
|
64
|
+
# Sorting sucks because we can't compare Strings and Symbols. So in
|
65
|
+
# order to get this right, we'll have to sort each type individually,
|
66
|
+
# then concatenate the results.
|
67
|
+
sorted = []
|
68
|
+
[Integer, Symbol, String].each do |klass|
|
69
|
+
sorted += keys.select { |key| key.is_a?(klass) }.sort(&block)
|
70
|
+
end
|
71
|
+
return sorted
|
72
|
+
end
|
73
|
+
|
74
|
+
READ_METHODS = ::Collapsium::Support::HashMethods::KEYED_READ_METHODS.freeze
|
75
|
+
|
76
|
+
INDIFFERENT_ACCESS_READER = proc do |wrapped_method, *args, &block|
|
77
|
+
# Bail out early if the receiver is not a Hash. Do the same if we have
|
78
|
+
# no key.
|
79
|
+
receiver = wrapped_method.receiver
|
80
|
+
if not receiver.is_a? Hash or args.empty?
|
81
|
+
next wrapped_method.call(*args, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Definitely try the key as given first. Then, depending on the key's
|
85
|
+
# type and value, we want to try it as a Symbol, String and/or Integer
|
86
|
+
key = args.shift
|
87
|
+
tries = IndifferentAccess.key_permutations(key)
|
88
|
+
|
89
|
+
# With the variations to try assembled, go through them one by one
|
90
|
+
result = nil
|
91
|
+
tries.each do |try|
|
92
|
+
if receiver.keys.include?(try)
|
93
|
+
result = wrapped_method.call(try, *args, &block)
|
94
|
+
break
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# If any of the above yielded a result, great, return that. Otherwise
|
99
|
+
# yield to the default implementation (i.e. wrapped_method).
|
100
|
+
if not result.nil?
|
101
|
+
next result
|
102
|
+
end
|
103
|
+
next wrapped_method.call(key, *args, &block)
|
104
|
+
end.freeze
|
105
|
+
|
106
|
+
def included(base)
|
107
|
+
enhance(base)
|
108
|
+
end
|
109
|
+
|
110
|
+
def extended(base)
|
111
|
+
enhance(base)
|
112
|
+
end
|
113
|
+
|
114
|
+
def prepended(base)
|
115
|
+
enhance(base)
|
116
|
+
end
|
117
|
+
|
118
|
+
def enhance(base)
|
119
|
+
# Make the capabilities of classes using IndifferentAccess viral.
|
120
|
+
base.extend(ViralCapabilities)
|
121
|
+
|
122
|
+
# Wrap all accessor functions to deal with paths
|
123
|
+
READ_METHODS.each do |method|
|
124
|
+
wrap_method(base, method, raise_on_missing: false,
|
125
|
+
&INDIFFERENT_ACCESS_READER)
|
126
|
+
end
|
29
127
|
end
|
30
|
-
end
|
128
|
+
end # class << self
|
31
129
|
|
32
130
|
end # module IndifferentAccess
|
33
131
|
|
@@ -84,7 +84,7 @@ module Collapsium
|
|
84
84
|
# Otherwise we had pathed access, and only want to pass the last
|
85
85
|
# component to whatever method we're calling.
|
86
86
|
the_args = args
|
87
|
-
if not args[0].is_a?(Symbol)
|
87
|
+
if not args[0].is_a?(Symbol) and args[0] != components.last
|
88
88
|
the_args = args.dup
|
89
89
|
the_args[0] = components.last
|
90
90
|
end
|
@@ -92,7 +92,8 @@ module Collapsium
|
|
92
92
|
# Array methods we're modifying here are indexed, so the first argument
|
93
93
|
# must be an integer. Let's make it so :)
|
94
94
|
if leaf.is_a? Array and the_args[0][0] =~ /[0-9]/
|
95
|
-
the_args
|
95
|
+
the_args = the_args.dup
|
96
|
+
the_args[0] = the_args[0].to_s.to_i
|
96
97
|
end
|
97
98
|
|
98
99
|
# Then we can continue with that method.
|
@@ -34,21 +34,11 @@ module Collapsium
|
|
34
34
|
return self
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
next v1 + v2
|
43
|
-
end
|
44
|
-
if overwrite
|
45
|
-
next v2
|
46
|
-
else
|
47
|
-
next v1
|
48
|
-
end
|
49
|
-
# rubocop:enable Style/GuardClause
|
50
|
-
end
|
51
|
-
merge!(other, &merger)
|
37
|
+
# We can't call merge! because that will only be invoked for keys that
|
38
|
+
# are missing, and default_proc doesn't seem to be used there. So we need
|
39
|
+
# to call a custom merge function.
|
40
|
+
new_self = RecursiveMerge.merger(self, self, other, overwrite)
|
41
|
+
replace(new_self)
|
52
42
|
end
|
53
43
|
|
54
44
|
##
|
@@ -57,5 +47,46 @@ module Collapsium
|
|
57
47
|
def recursive_merge(other, overwrite = true)
|
58
48
|
return dup.recursive_merge!(other, overwrite)
|
59
49
|
end
|
50
|
+
|
51
|
+
class << self
|
52
|
+
def merger(the_self, v1, v2, overwrite)
|
53
|
+
if v1.is_a? Hash and v2.is_a? Hash
|
54
|
+
v1 = ViralCapabilities.enhance_value(the_self, v1)
|
55
|
+
v2 = ViralCapabilities.enhance_value(the_self, v2)
|
56
|
+
|
57
|
+
# IndifferentAccess has its own idea of which keys are unique, so if
|
58
|
+
# we use it, we must consult it.
|
59
|
+
keys = (v1.keys + v2.keys).uniq
|
60
|
+
if the_self.singleton_class.ancestors.include?(IndifferentAccess)
|
61
|
+
keys = IndifferentAccess.unique_keys(keys)
|
62
|
+
end
|
63
|
+
new_val = ViralCapabilities.enhance_value(the_self, {})
|
64
|
+
keys.each do |key|
|
65
|
+
v1_inner = v1[key]
|
66
|
+
v2_inner = v2[key]
|
67
|
+
if not v1_inner.nil? and not v2_inner.nil?
|
68
|
+
new_val[key] = RecursiveMerge.merger(the_self, v1_inner, v2_inner,
|
69
|
+
overwrite)
|
70
|
+
elsif not v1_inner.nil?
|
71
|
+
# Nothing to do, we have v1[key]
|
72
|
+
new_val[key] = v1_inner
|
73
|
+
else
|
74
|
+
# v2.key?(key) is true
|
75
|
+
new_val[key] = v2_inner
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
v1.replace(new_val)
|
80
|
+
return v1
|
81
|
+
elsif v1.is_a? Array and v2.is_a? Array
|
82
|
+
return v1 + v2
|
83
|
+
end
|
84
|
+
|
85
|
+
if overwrite
|
86
|
+
return v2
|
87
|
+
end
|
88
|
+
return v1
|
89
|
+
end
|
90
|
+
end # class << self
|
60
91
|
end # module RecursiveMerge
|
61
92
|
end # module Collapsium
|
@@ -8,6 +8,7 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
require 'collapsium/viral_capabilities'
|
11
|
+
require 'collapsium/indifferent_access'
|
11
12
|
|
12
13
|
module Collapsium
|
13
14
|
##
|
@@ -21,7 +22,15 @@ module Collapsium
|
|
21
22
|
# Recursively sort a Hash by its keys. Without a block, this function will
|
22
23
|
# not be able to compare keys of different size.
|
23
24
|
def recursive_sort!(&block)
|
24
|
-
|
25
|
+
# If we have IndifferentAccess, we need to sort keys appropriately.
|
26
|
+
the_keys = nil
|
27
|
+
if singleton_class.ancestors.include?(IndifferentAccess)
|
28
|
+
the_keys = IndifferentAccess.sorted_keys(keys, &block)
|
29
|
+
else
|
30
|
+
the_keys = keys.sort(&block)
|
31
|
+
end
|
32
|
+
|
33
|
+
return the_keys.reduce(self) do |seed, key|
|
25
34
|
# Delete (and later re-insert) value for ordering
|
26
35
|
value = self[key]
|
27
36
|
delete(key)
|
@@ -7,6 +7,8 @@
|
|
7
7
|
# All rights reserved.
|
8
8
|
#
|
9
9
|
|
10
|
+
require 'zlib'
|
11
|
+
|
10
12
|
module Collapsium
|
11
13
|
|
12
14
|
##
|
@@ -18,6 +20,53 @@ module Collapsium
|
|
18
20
|
module Methods
|
19
21
|
WRAPPER_HASH = "@__collapsium_methods_wrappers".freeze
|
20
22
|
|
23
|
+
# Try to determine built-in Classes and Modules early on, so we
|
24
|
+
# can ignore them in our wrappers search.
|
25
|
+
class << self
|
26
|
+
##
|
27
|
+
# Return built-in symbols. It's called to initialize the BUILTINS
|
28
|
+
# constant early on. It also caches its result, so should be safe to
|
29
|
+
# call later, too.
|
30
|
+
def builtins
|
31
|
+
@builtins ||= nil
|
32
|
+
if not @builtins.nil?
|
33
|
+
return @builtins
|
34
|
+
end
|
35
|
+
|
36
|
+
# Object's constants contain all Module and Class definitions to date.
|
37
|
+
# We need to get the constant values, though, not just their names.
|
38
|
+
builtins = Object.constants.sort.map do |const_name|
|
39
|
+
Object.const_get(const_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# If JSON was required, there will be some generator methods that
|
43
|
+
# override the above generators. We want to filter those out as well.
|
44
|
+
# If, however, JSON was not required, we'll get a NameError and won't
|
45
|
+
# add anything new.
|
46
|
+
# rubocop:disable Lint/HandleExceptions
|
47
|
+
begin
|
48
|
+
json_builtins = JSON::Ext::Generator::GeneratorMethods.constants.sort
|
49
|
+
json_builtins.map! do |const_name|
|
50
|
+
JSON::Ext::Generator::GeneratorMethods.const_get(const_name)
|
51
|
+
end
|
52
|
+
builtins += json_builtins
|
53
|
+
rescue NameError
|
54
|
+
# We just ignore this; if JSON is automatically required, there will
|
55
|
+
# be some mixin Modules here, otherwise we will just process them.
|
56
|
+
end
|
57
|
+
# rubocop:enable Lint/HandleExceptions
|
58
|
+
|
59
|
+
# Last, we want to filter, so only Class and Module items are kept.
|
60
|
+
builtins.select! do |item|
|
61
|
+
item.is_a?(Module) or item.is_a?(Class)
|
62
|
+
end
|
63
|
+
|
64
|
+
@builtins = builtins
|
65
|
+
return @builtins
|
66
|
+
end
|
67
|
+
end # class << self
|
68
|
+
BUILTINS = Methods.builtins.freeze
|
69
|
+
|
21
70
|
##
|
22
71
|
# Given the base module, wraps the given method name in the given block.
|
23
72
|
# The block must accept the wrapped_method as the first parameter, followed
|
@@ -63,7 +112,6 @@ module Collapsium
|
|
63
112
|
|
64
113
|
# Our current binding is based on the wrapper block and our own class,
|
65
114
|
# as well as the arguments (CRC32).
|
66
|
-
require 'zlib'
|
67
115
|
signature = Zlib.crc32(args.to_s)
|
68
116
|
the_binding = [wrapper_block.object_id, self.class.object_id, signature]
|
69
117
|
|
@@ -145,7 +193,7 @@ module Collapsium
|
|
145
193
|
# Given any base (value, class, module) and a method name, returns the
|
146
194
|
# wrappers defined for the base, in order of definition. If no wrappers
|
147
195
|
# are defined, an empty Array is returned.
|
148
|
-
def wrappers(base, method_name, visited =
|
196
|
+
def wrappers(base, method_name, visited = nil)
|
149
197
|
# First, check the instance, then its class for a wrapper. If either of
|
150
198
|
# them succeeds, exit with a result.
|
151
199
|
[base, base.class].each do |item|
|
@@ -155,10 +203,6 @@ module Collapsium
|
|
155
203
|
end
|
156
204
|
end
|
157
205
|
|
158
|
-
# We add the base and its class to the set of visited items.
|
159
|
-
visited.add(base)
|
160
|
-
visited.add(base.class)
|
161
|
-
|
162
206
|
# If neither of the above contained a wrapper, look at ancestors
|
163
207
|
# recursively.
|
164
208
|
ancestors = nil
|
@@ -167,7 +211,21 @@ module Collapsium
|
|
167
211
|
rescue NoMethodError
|
168
212
|
ancestors = base.class.ancestors
|
169
213
|
end
|
170
|
-
ancestors = ancestors - Object.ancestors -
|
214
|
+
ancestors = ancestors - Object.ancestors - BUILTINS
|
215
|
+
|
216
|
+
# Bail out if there are no ancestors to process.
|
217
|
+
if ancestors.empty?
|
218
|
+
return []
|
219
|
+
end
|
220
|
+
|
221
|
+
# We add the base and its class to the set of visited items. Note
|
222
|
+
# that we're doing it late, so we only have to do it when we have
|
223
|
+
# ancestors to visit.
|
224
|
+
if visited.nil?
|
225
|
+
visited = Set.new
|
226
|
+
end
|
227
|
+
visited.add(base)
|
228
|
+
visited.add(base.class)
|
171
229
|
|
172
230
|
ancestors.each do |ancestor|
|
173
231
|
# Skip an visited item...
|
data/lib/collapsium/uber_hash.rb
CHANGED
@@ -28,6 +28,7 @@ module Collapsium
|
|
28
28
|
include RecursiveSort
|
29
29
|
include RecursiveFetch
|
30
30
|
include PathedAccess
|
31
|
+
include IndifferentAccess
|
31
32
|
include PrototypeMatch
|
32
33
|
|
33
34
|
include Support::HashMethods
|
@@ -35,9 +36,6 @@ module Collapsium
|
|
35
36
|
def initialize(*args)
|
36
37
|
super
|
37
38
|
|
38
|
-
# Activate IndifferentAccess
|
39
|
-
self.default_proc = IndifferentAccess::DEFAULT_PROC
|
40
|
-
|
41
39
|
# Extra functionality: allow being initialized by a Hash
|
42
40
|
if args.empty? or not args[0].is_a?(Hash)
|
43
41
|
return
|
data/lib/collapsium/version.rb
CHANGED
@@ -35,6 +35,8 @@ module Collapsium
|
|
35
35
|
array_ancestor: Array,
|
36
36
|
}.freeze
|
37
37
|
|
38
|
+
ENHANCED_MARKER = "@__collapsium_viral_capabilities_marker".freeze
|
39
|
+
|
38
40
|
##
|
39
41
|
# Any Object (Class, Module) that's enhanced with ViralCapabilities will at
|
40
42
|
# least be extended with a module defining its Hash and Array ancestors.
|
@@ -138,22 +140,14 @@ module Collapsium
|
|
138
140
|
# It's possible that the value is a Hash or an Array, but there's no
|
139
141
|
# ancestor from which capabilities can be copied. We can find out by
|
140
142
|
# checking whether any wrappers are defined for it.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
if
|
146
|
-
|
147
|
-
|
148
|
-
break
|
143
|
+
# Turns out that that's quite expensive at run-time, though, so instead
|
144
|
+
# we resort to flagging enhanced values.
|
145
|
+
if value.is_a? Array or value.is_a? Hash
|
146
|
+
needs_wrapping = !value.instance_variable_get(ENHANCED_MARKER)
|
147
|
+
if needs_wrapping
|
148
|
+
enhance(value)
|
149
|
+
value.instance_variable_set(ENHANCED_MARKER, true)
|
149
150
|
end
|
150
|
-
# rubocop:enable Style/Next
|
151
|
-
end
|
152
|
-
|
153
|
-
# If we have a Hash or Array value that needs enhancing still, let's
|
154
|
-
# do that.
|
155
|
-
if needs_wrapping and (value.is_a? Array or value.is_a? Hash)
|
156
|
-
enhance(value)
|
157
151
|
end
|
158
152
|
|
159
153
|
return value
|
@@ -1,26 +1,121 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require_relative '../lib/collapsium/indifferent_access'
|
3
3
|
|
4
|
+
class IncludedIndifferentHash < Hash
|
5
|
+
include ::Collapsium::IndifferentAccess
|
6
|
+
end
|
7
|
+
|
8
|
+
class PrependedIndifferentHash < Hash
|
9
|
+
prepend ::Collapsium::IndifferentAccess
|
10
|
+
end
|
11
|
+
|
12
|
+
class ExtendedIndifferentHash < Hash
|
13
|
+
extend ::Collapsium::IndifferentAccess
|
14
|
+
end
|
15
|
+
|
4
16
|
describe ::Collapsium::IndifferentAccess do
|
5
|
-
|
6
|
-
|
7
|
-
|
17
|
+
let(:tester) do
|
18
|
+
tester = {}
|
19
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
20
|
+
tester
|
8
21
|
end
|
9
22
|
|
10
23
|
it "allows accessing string keys via symbol" do
|
11
|
-
|
12
|
-
|
13
|
-
|
24
|
+
expect(tester).to be_empty
|
25
|
+
|
26
|
+
tester["foo"] = 42
|
27
|
+
expect(tester["foo"]).to eql 42
|
28
|
+
expect(tester[:foo]).to eql 42
|
14
29
|
end
|
15
30
|
|
16
31
|
it "allows accessing symbol keys via strings" do
|
17
|
-
|
18
|
-
|
19
|
-
|
32
|
+
expect(tester).to be_empty
|
33
|
+
|
34
|
+
tester[:foo] = 42
|
35
|
+
expect(tester[:foo]).to eql 42
|
36
|
+
expect(tester["foo"]).to eql 42
|
37
|
+
end
|
38
|
+
|
39
|
+
it "allows accessing integer keys with strings or symbols" do
|
40
|
+
expect(tester).to be_empty
|
41
|
+
|
42
|
+
tester[42] = 123
|
43
|
+
expect(tester[:"42"]).to eql 123
|
44
|
+
expect(tester["42"]).to eql 123
|
45
|
+
end
|
46
|
+
|
47
|
+
it "allows accessing string keys with integers" do
|
48
|
+
expect(tester).to be_empty
|
49
|
+
|
50
|
+
tester["42"] = 123
|
51
|
+
expect(tester[:"42"]).to eql 123
|
52
|
+
expect(tester[42]).to eql 123
|
20
53
|
end
|
21
54
|
|
22
55
|
it "still works with other keys" do
|
23
|
-
|
24
|
-
|
56
|
+
expect(tester).to be_empty
|
57
|
+
|
58
|
+
tester[42] = "foo"
|
59
|
+
|
60
|
+
expect(tester[nil]).to be_nil
|
61
|
+
expect(tester[3.14]).to be_nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it "makes keys unique" do
|
65
|
+
test = [
|
66
|
+
"foo", :foo,
|
67
|
+
"42", :"42", 42,
|
68
|
+
]
|
69
|
+
expect(::Collapsium::IndifferentAccess.unique_keys(test)).to eql %w(foo 42)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "can sort keys" do
|
73
|
+
test = [
|
74
|
+
"foo", :foo,
|
75
|
+
"bar", :bar,
|
76
|
+
"42", :"42", 42,
|
77
|
+
]
|
78
|
+
expecation = [
|
79
|
+
42,
|
80
|
+
:"42", :bar, :foo,
|
81
|
+
"42", "bar", "foo",
|
82
|
+
]
|
83
|
+
expect(::Collapsium::IndifferentAccess.sorted_keys(test)).to eql expecation
|
84
|
+
end
|
85
|
+
|
86
|
+
it "does nothing for Arrays" do
|
87
|
+
test = [42]
|
88
|
+
test.extend(::Collapsium::IndifferentAccess)
|
89
|
+
|
90
|
+
result = nil
|
91
|
+
expect { result = test.include?(42) }.not_to raise_error
|
92
|
+
expect(result).to be_truthy
|
93
|
+
end
|
94
|
+
|
95
|
+
context IncludedIndifferentHash do
|
96
|
+
let(:tester) { IncludedIndifferentHash.new }
|
97
|
+
|
98
|
+
it "can be accessed indifferently" do
|
99
|
+
tester["foo"] = 42
|
100
|
+
expect(tester[:foo]).to eql 42
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context ExtendedIndifferentHash do
|
105
|
+
let(:tester) { ExtendedIndifferentHash.new }
|
106
|
+
|
107
|
+
it "can be accessed indifferently" do
|
108
|
+
tester["foo"] = 42
|
109
|
+
expect(tester[:foo]).to eql 42
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context PrependedIndifferentHash do
|
114
|
+
let(:tester) { PrependedIndifferentHash.new }
|
115
|
+
|
116
|
+
it "can be accessed indifferently" do
|
117
|
+
tester["foo"] = 42
|
118
|
+
expect(tester[:foo]).to eql 42
|
119
|
+
end
|
25
120
|
end
|
26
121
|
end
|
data/spec/pathed_access_spec.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require_relative '../lib/collapsium/pathed_access'
|
3
|
+
require_relative '../lib/collapsium/indifferent_access'
|
3
4
|
|
4
5
|
class PathedHash < Hash
|
5
6
|
prepend ::Collapsium::PathedAccess
|
@@ -34,7 +35,7 @@ describe ::Collapsium::PathedAccess do
|
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
|
-
describe "
|
38
|
+
describe "PathedAccess" do
|
38
39
|
context ":path_prefix" do
|
39
40
|
it "can be read" do
|
40
41
|
expect { @tester.path_prefix }.not_to raise_error
|
@@ -145,6 +146,22 @@ describe ::Collapsium::PathedAccess do
|
|
145
146
|
# "foo.bar" also exists at the top level.
|
146
147
|
expect(@tester["foo.bar"]).to be_nil
|
147
148
|
end
|
149
|
+
|
150
|
+
context "keys with spaces" do
|
151
|
+
it "accepts spaces in top-level keys" do
|
152
|
+
@tester['foo bar'] = 42
|
153
|
+
expect(@tester['foo bar']).to eql 42
|
154
|
+
expect(@tester['.foo bar']).to eql 42
|
155
|
+
end
|
156
|
+
|
157
|
+
it "accepts spaces in nested keys" do
|
158
|
+
@tester['spaces'] = {
|
159
|
+
'foo bar' => 42,
|
160
|
+
}
|
161
|
+
expect(@tester['spaces']['foo bar']).to eql 42
|
162
|
+
expect(@tester['spaces.foo bar']).to eql 42
|
163
|
+
end
|
164
|
+
end
|
148
165
|
end
|
149
166
|
|
150
167
|
describe "nested inherit capabilities" do
|
@@ -156,7 +173,7 @@ describe ::Collapsium::PathedAccess do
|
|
156
173
|
}
|
157
174
|
end
|
158
175
|
|
159
|
-
it "can still perform
|
176
|
+
it "can still perform PathedAccess" do
|
160
177
|
foo = @tester['foo']
|
161
178
|
expect(foo['bar.baz']).to eql 42
|
162
179
|
end
|
@@ -167,41 +184,6 @@ describe ::Collapsium::PathedAccess do
|
|
167
184
|
end
|
168
185
|
end
|
169
186
|
|
170
|
-
describe "with indifferent access" do
|
171
|
-
before do
|
172
|
-
require_relative '../lib/collapsium/indifferent_access'
|
173
|
-
end
|
174
|
-
|
175
|
-
it "can write with indifferent access without overwriting" do
|
176
|
-
@tester[:foo] = {
|
177
|
-
bar: 42,
|
178
|
-
baz: 'quux',
|
179
|
-
}
|
180
|
-
@tester.default_proc = ::Collapsium::IndifferentAccess::DEFAULT_PROC
|
181
|
-
|
182
|
-
expect(@tester['foo.bar']).to eql 42
|
183
|
-
expect(@tester['foo.baz']).to eql 'quux'
|
184
|
-
|
185
|
-
@tester['foo.bar'] = 123
|
186
|
-
expect(@tester['foo.bar']).to eql 123
|
187
|
-
expect(@tester['foo.baz']).to eql 'quux'
|
188
|
-
end
|
189
|
-
|
190
|
-
it "doesn't break #path_prefix" do
|
191
|
-
@tester[:foo] = {
|
192
|
-
bar: {
|
193
|
-
baz: 123,
|
194
|
-
}
|
195
|
-
}
|
196
|
-
@tester.default_proc = ::Collapsium::IndifferentAccess::DEFAULT_PROC
|
197
|
-
|
198
|
-
expect(@tester[:foo].path_prefix).to eql ".foo"
|
199
|
-
expect(@tester["foo"].path_prefix).to eql ".foo"
|
200
|
-
expect(@tester[:foo][:bar].path_prefix).to eql ".foo.bar"
|
201
|
-
expect(@tester["foo.bar"].path_prefix).to eql ".foo.bar"
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
187
|
context PathedHash do
|
206
188
|
let(:test_hash) { PathedHash.new }
|
207
189
|
|
@@ -230,26 +212,100 @@ describe ::Collapsium::PathedAccess do
|
|
230
212
|
}
|
231
213
|
end
|
232
214
|
|
233
|
-
it "resolved with
|
215
|
+
it "resolved with PathedAccess" do
|
234
216
|
expect(@tester['foo.bar.0.baz1']).to eql 'quux1'
|
235
217
|
expect(@tester['foo.bar.1.baz2']).to eql 'quux2'
|
236
218
|
end
|
237
219
|
end
|
238
220
|
|
239
|
-
context "
|
240
|
-
|
241
|
-
|
242
|
-
|
221
|
+
context "with IndifferentAccess" do
|
222
|
+
it "resolves with a created Hash and :[]" do
|
223
|
+
tester = { foo: { 'bar' => 42 } }
|
224
|
+
tester.extend(::Collapsium::PathedAccess)
|
225
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
226
|
+
|
227
|
+
res = tester['.foo']
|
228
|
+
|
229
|
+
expect(res.length).to eql 1
|
230
|
+
expect(res.key?('bar')).to be_truthy
|
231
|
+
expect(res.fetch('bar')).to eql 42
|
232
|
+
expect(res['bar']).to eql 42
|
233
|
+
end
|
234
|
+
|
235
|
+
it "resolves with a created Hash and :fetch" do
|
236
|
+
tester = { foo: { 'bar' => 42 } }
|
237
|
+
tester.extend(::Collapsium::PathedAccess)
|
238
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
239
|
+
|
240
|
+
res = tester.fetch('.foo')
|
241
|
+
|
242
|
+
expect(res.length).to eql 1
|
243
|
+
expect(res.key?('bar')).to be_truthy
|
244
|
+
expect(res.fetch('bar')).to eql 42
|
245
|
+
expect(res['bar']).to eql 42
|
246
|
+
end
|
247
|
+
|
248
|
+
it "resolves with a replaced Hash and :[]" do
|
249
|
+
tester = {}
|
250
|
+
tester.extend(::Collapsium::PathedAccess)
|
251
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
252
|
+
tester.replace(foo: { 'bar' => 42 })
|
253
|
+
|
254
|
+
res = tester['.foo']
|
255
|
+
|
256
|
+
expect(res.length).to eql 1
|
257
|
+
expect(res.key?('bar')).to be_truthy
|
258
|
+
expect(res.fetch('bar')).to eql 42
|
259
|
+
expect(res['bar']).to eql 42
|
260
|
+
end
|
261
|
+
|
262
|
+
it "resolves with a replaced Hash and :fetch" do
|
263
|
+
tester = {}
|
264
|
+
tester.extend(::Collapsium::PathedAccess)
|
265
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
266
|
+
tester.replace(foo: { 'bar' => 42 })
|
267
|
+
|
268
|
+
res = tester.fetch('.foo')
|
269
|
+
|
270
|
+
expect(res.length).to eql 1
|
271
|
+
expect(res.key?('bar')).to be_truthy
|
272
|
+
expect(res.fetch('bar')).to eql 42
|
273
|
+
expect(res['bar']).to eql 42
|
274
|
+
end
|
275
|
+
|
276
|
+
it "can write without overwriting" do
|
277
|
+
tester = {
|
278
|
+
foo: {
|
279
|
+
bar: 42,
|
280
|
+
baz: 'quux',
|
281
|
+
}
|
243
282
|
}
|
283
|
+
tester.extend(::Collapsium::PathedAccess)
|
284
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
285
|
+
|
286
|
+
expect(tester['foo.bar']).to eql 42
|
287
|
+
expect(tester['foo.baz']).to eql 'quux'
|
288
|
+
|
289
|
+
tester['foo.bar'] = 123
|
290
|
+
expect(tester['foo.bar']).to eql 123
|
291
|
+
expect(tester['foo.baz']).to eql 'quux'
|
244
292
|
end
|
245
293
|
|
246
|
-
it "
|
247
|
-
|
248
|
-
|
294
|
+
it "doesn't break #path_prefix" do
|
295
|
+
tester = {
|
296
|
+
foo: {
|
297
|
+
bar: {
|
298
|
+
baz: 123,
|
299
|
+
}
|
300
|
+
}
|
301
|
+
}
|
302
|
+
tester.extend(::Collapsium::PathedAccess)
|
303
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
249
304
|
|
250
|
-
|
251
|
-
|
252
|
-
expect(
|
305
|
+
expect(tester[:foo].path_prefix).to eql ".foo"
|
306
|
+
expect(tester["foo"].path_prefix).to eql ".foo"
|
307
|
+
expect(tester[:foo][:bar].path_prefix).to eql ".foo.bar"
|
308
|
+
expect(tester["foo.bar"].path_prefix).to eql ".foo.bar"
|
253
309
|
end
|
254
310
|
end
|
255
311
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require_relative '../lib/collapsium/recursive_merge'
|
3
|
+
require_relative '../lib/collapsium/indifferent_access'
|
4
|
+
require_relative '../lib/collapsium/pathed_access'
|
3
5
|
|
4
6
|
describe ::Collapsium::RecursiveMerge do
|
5
7
|
before :each do
|
@@ -84,4 +86,119 @@ describe ::Collapsium::RecursiveMerge do
|
|
84
86
|
expect(@tester[:foo].respond_to?(:recursive_merge)).to be_truthy
|
85
87
|
expect(x[:foo].respond_to?(:recursive_merge)).to be_truthy
|
86
88
|
end
|
89
|
+
|
90
|
+
it "does not offer indifferent access by itself" do
|
91
|
+
tester = {
|
92
|
+
foo: 42
|
93
|
+
}
|
94
|
+
tester.extend(::Collapsium::RecursiveMerge)
|
95
|
+
|
96
|
+
expect(tester[:foo]).to eql 42
|
97
|
+
expect(tester['foo']).to be_nil
|
98
|
+
end
|
99
|
+
|
100
|
+
context "with IndifferentAccess" do
|
101
|
+
let(:tester) do
|
102
|
+
tester = {}
|
103
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
104
|
+
tester.extend(::Collapsium::RecursiveMerge)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "merges string and symbol keys" do
|
108
|
+
tester[:foo] = {
|
109
|
+
bar: 123,
|
110
|
+
}
|
111
|
+
tester[:arr] = [2]
|
112
|
+
other = {
|
113
|
+
"foo" => {
|
114
|
+
"baz asdf" => "quux",
|
115
|
+
"bar" => 42
|
116
|
+
},
|
117
|
+
"arr" => [1],
|
118
|
+
}
|
119
|
+
x = tester.recursive_merge(other)
|
120
|
+
|
121
|
+
expect(x.length).to eql 2
|
122
|
+
expect(x[:foo].length).to eql 2
|
123
|
+
expect(x[:foo]['baz asdf']).to eql 'quux'
|
124
|
+
expect(x[:foo][:bar]).to eql 42 # overwrite
|
125
|
+
expect(x[:arr].length).to eql 2
|
126
|
+
expect(x[:arr]).to eql [2, 1]
|
127
|
+
end
|
128
|
+
|
129
|
+
it "merges string and symbol keys without overwriting" do
|
130
|
+
tester[:foo] = {
|
131
|
+
bar: 123,
|
132
|
+
}
|
133
|
+
tester[:arr] = [2]
|
134
|
+
other = {
|
135
|
+
"foo" => {
|
136
|
+
"baz asdf" => "quux",
|
137
|
+
"bar" => 42
|
138
|
+
},
|
139
|
+
"arr" => [1],
|
140
|
+
}
|
141
|
+
x = tester.recursive_merge(other, false)
|
142
|
+
|
143
|
+
expect(x.length).to eql 2
|
144
|
+
expect(x[:foo].length).to eql 2
|
145
|
+
expect(x[:foo]['baz asdf']).to eql 'quux'
|
146
|
+
expect(x[:foo][:bar]).to eql 123 # no overwrite
|
147
|
+
expect(x[:arr].length).to eql 2
|
148
|
+
expect(x[:arr]).to eql [2, 1]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "with IndifferentAccess and PathedAccess" do
|
153
|
+
let(:tester) do
|
154
|
+
tester = {}
|
155
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
156
|
+
tester.extend(::Collapsium::RecursiveMerge)
|
157
|
+
tester.extend(::Collapsium::PathedAccess)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "merges string and symbol keys" do
|
161
|
+
tester[:foo] = {
|
162
|
+
bar: 123,
|
163
|
+
}
|
164
|
+
tester[:arr] = [2]
|
165
|
+
other = {
|
166
|
+
"foo" => {
|
167
|
+
"baz asdf" => "quux",
|
168
|
+
"bar" => 42
|
169
|
+
},
|
170
|
+
"arr" => [1],
|
171
|
+
}
|
172
|
+
x = tester.recursive_merge(other)
|
173
|
+
|
174
|
+
expect(x.length).to eql 2
|
175
|
+
expect(x['foo'].length).to eql 2
|
176
|
+
expect(x['foo.baz asdf']).to eql 'quux'
|
177
|
+
expect(x['foo.bar']).to eql 42 # overwrite
|
178
|
+
expect(x['arr'].length).to eql 2
|
179
|
+
expect(x['arr']).to eql [2, 1]
|
180
|
+
end
|
181
|
+
|
182
|
+
it "merges string and symbol keys without overwriting" do
|
183
|
+
tester[:foo] = {
|
184
|
+
bar: 123,
|
185
|
+
}
|
186
|
+
tester[:arr] = [2]
|
187
|
+
other = {
|
188
|
+
"foo" => {
|
189
|
+
"baz asdf" => "quux",
|
190
|
+
"bar" => 42
|
191
|
+
},
|
192
|
+
"arr" => [1],
|
193
|
+
}
|
194
|
+
x = tester.recursive_merge(other, false)
|
195
|
+
|
196
|
+
expect(x.length).to eql 2
|
197
|
+
expect(x['foo'].length).to eql 2
|
198
|
+
expect(x['foo.baz asdf']).to eql 'quux'
|
199
|
+
expect(x['foo.bar']).to eql 123 # no overwrite
|
200
|
+
expect(x['arr'].length).to eql 2
|
201
|
+
expect(x['arr']).to eql [2, 1]
|
202
|
+
end
|
203
|
+
end
|
87
204
|
end
|
data/spec/recursive_sort_spec.rb
CHANGED
@@ -2,11 +2,6 @@ require 'spec_helper'
|
|
2
2
|
require_relative '../lib/collapsium/recursive_sort'
|
3
3
|
|
4
4
|
describe ::Collapsium::RecursiveSort do
|
5
|
-
before :each do
|
6
|
-
@tester = {}
|
7
|
-
@tester.extend(::Collapsium::RecursiveSort)
|
8
|
-
end
|
9
|
-
|
10
5
|
it "sorts a nested hash with string keys in place" do
|
11
6
|
h = {
|
12
7
|
'a' => 1,
|
@@ -80,4 +75,18 @@ describe ::Collapsium::RecursiveSort do
|
|
80
75
|
expect(h.object_id).not_to eql h2.object_id
|
81
76
|
expect(h['c'].object_id).not_to eql h2['c'].object_id
|
82
77
|
end
|
78
|
+
|
79
|
+
it "works with IndifferentAccess" do
|
80
|
+
require_relative '../lib/collapsium/indifferent_access'
|
81
|
+
tester = {
|
82
|
+
"foo" => 42,
|
83
|
+
"bar" => 123,
|
84
|
+
:foo => "foo from symbol",
|
85
|
+
:bar => "bar from symbol",
|
86
|
+
}
|
87
|
+
tester.extend(::Collapsium::RecursiveSort)
|
88
|
+
tester.extend(::Collapsium::IndifferentAccess)
|
89
|
+
|
90
|
+
expect { tester.recursive_sort! }.not_to raise_error
|
91
|
+
end
|
83
92
|
end
|
data/spec/spec_helper.rb
CHANGED
File without changes
|
data/spec/uber_hash_spec.rb
CHANGED
@@ -74,4 +74,9 @@ describe Collapsium::UberHash do
|
|
74
74
|
x = ::Collapsium::UberHash.new(data)
|
75
75
|
expect(x[:some].is_a?(::Collapsium::UberHash)).to be_truthy
|
76
76
|
end
|
77
|
+
|
78
|
+
it "behaves like a Hash" do
|
79
|
+
tester = ::Collapsium::UberHash.new
|
80
|
+
expect { tester[] }.to raise_error(ArgumentError)
|
81
|
+
end
|
77
82
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: collapsium
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Finkhaeuser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-11-
|
11
|
+
date: 2016-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -139,7 +139,7 @@ files:
|
|
139
139
|
- spec/recursive_sort_spec.rb
|
140
140
|
- spec/spec_helper.rb
|
141
141
|
- spec/support_methods_spec.rb
|
142
|
-
- spec/
|
142
|
+
- spec/support_path_components_spec.rb
|
143
143
|
- spec/uber_hash_spec.rb
|
144
144
|
- spec/viral_capabilities_spec.rb
|
145
145
|
homepage: https://github.com/jfinkhaeuser/collapsium
|
@@ -177,6 +177,6 @@ test_files:
|
|
177
177
|
- spec/recursive_sort_spec.rb
|
178
178
|
- spec/spec_helper.rb
|
179
179
|
- spec/support_methods_spec.rb
|
180
|
-
- spec/
|
180
|
+
- spec/support_path_components_spec.rb
|
181
181
|
- spec/uber_hash_spec.rb
|
182
182
|
- spec/viral_capabilities_spec.rb
|