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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63b5ae2bec3820d536814099c538f53f32ce3d14
4
- data.tar.gz: d18f3d4bd528f98cf7b92a31a94da97211efb5d2
3
+ metadata.gz: a0237b4fe627fff71b5c02f5e3f77f9d9713e849
4
+ data.tar.gz: fd48fd69e118edfba70a91ee7920495008a140ad
5
5
  SHA512:
6
- metadata.gz: 1bc5ee36f8b47a7a7e1acffa6d80ef1d4dc400e4459418542e7dfaf99fed4d9fb76493ade6e37f5b50faecaad99a6c641ec7f6f78ece791f225269750ba4e16a
7
- data.tar.gz: 7c5dc2d3f39681cfb909bc4259ca577707c90884e8bd3a80819ad5b84e79689d57fb1850856b25cf7bc18245a350f24b1037d324ce13e3ea63a090fbef848088
6
+ metadata.gz: 69e87ad355d708944ef6bda61139a63028a666cf9165b2438c2b47da3edc92fdaa4b581435528ef31754b09cd265e2908dc3205c049e9ea1424b4d7b93536cfe
7
+ data.tar.gz: f85612ff08aeaf56ca7699a68ef171709905aa838eabf26c7196002b95529cb79eeff998a5a546a70cf46d64485842cc913449e4d073a51e441ba1d50102dec2
data/.travis.yml CHANGED
@@ -3,6 +3,9 @@ rvm:
3
3
  - 2.0
4
4
  - 2.1
5
5
  - 2.2
6
+ script:
7
+ - bundle exec rake
8
+ - bundle exec codeclimate-test-reporter
6
9
  addons:
7
10
  code_climate:
8
11
  repo_token: 56724b4322c0a98f681db40ccfa92d1a9fd4755a18daf82683f95fcaa058499d
data/Gemfile.lock CHANGED
@@ -1,18 +1,18 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- collapsium (0.6.1)
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.6.0)
11
- simplecov (>= 0.7.1, < 1.0.0)
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.1.4)
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
- # Set your Hash's #default_proc to DEFAULT_PROC, and you've got indifferent
20
- # access.
21
- DEFAULT_PROC = proc do |hash, key|
22
- case key
23
- when String
24
- sym = key.to_sym
25
- hash[sym] if hash.key?(sym)
26
- when Symbol
27
- str = key.to_s
28
- hash[str] if hash.key?(str)
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.freeze
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[0] = the_args[0].to_i
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
- merger = proc do |_, v1, v2|
38
- # rubocop:disable Style/GuardClause
39
- if v1.is_a? Hash and v2.is_a? Hash
40
- next v1.merge!(v2, &merger)
41
- elsif v1.is_a? Array and v2.is_a? Array
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
- return keys.sort(&block).reduce(self) do |seed, key|
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 = Set.new)
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 - [Object, Class, Module]
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...
@@ -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
@@ -8,5 +8,5 @@
8
8
  #
9
9
  module Collapsium
10
10
  # The current release version
11
- VERSION = "0.6.1".freeze
11
+ VERSION = "0.7.0".freeze
12
12
  end
@@ -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
- needs_wrapping = true
142
- READ_METHODS.each do |method_name|
143
- wrappers = ::Collapsium::Support::Methods.wrappers(value, method_name)
144
- # rubocop:disable Style/Next
145
- if wrappers.include?(@@read_block)
146
- # all done
147
- needs_wrapping = false
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
- before :each do
6
- @tester = {}
7
- @tester.default_proc = ::Collapsium::IndifferentAccess::DEFAULT_PROC
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
- @tester["foo"] = 42
12
- expect(@tester["foo"]).to eql 42
13
- expect(@tester[:foo]).to eql 42
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
- @tester[:foo] = 42
18
- expect(@tester[:foo]).to eql 42
19
- expect(@tester["foo"]).to eql 42
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
- @tester[42] = "foo"
24
- expect(@tester[42]).to eql "foo"
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
@@ -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 "pathed access" do
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 pathed access" do
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 pathed access" do
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 "nested symbol keys" do
240
- before do
241
- @tester['foo'] = {
242
- bar: { 'baz' => 'quux' },
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 "resolve with pathed access & indifferent access" do
247
- # This should be nil - we don't use indifferent access yet.
248
- expect(@tester['foo.bar.baz']).to be_nil
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
- # With indifferent access, pathed access must work
251
- @tester.default_proc = ::Collapsium::IndifferentAccess::DEFAULT_PROC
252
- expect(@tester['foo.bar.baz']).to eql 'quux'
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
@@ -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
@@ -1,9 +1,3 @@
1
- # Only start CodeClimate from travis
2
- if ENV['CODECLIMATE_REPO_TOKEN']
3
- require 'codeclimate-test-reporter'
4
- CodeClimate::TestReporter.start
5
- end
6
-
7
1
  # Always start SimpleCov
8
2
  require 'simplecov'
9
3
  SimpleCov.start do
@@ -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.6.1
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-04 00:00:00.000000000 Z
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/support_path_components.rb
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/support_path_components.rb
180
+ - spec/support_path_components_spec.rb
181
181
  - spec/uber_hash_spec.rb
182
182
  - spec/viral_capabilities_spec.rb