collapsium 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|