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 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