right_support 2.2.2 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,255 @@
1
+ #
2
+ # Copyright (c) 2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightSupport::Data
24
+
25
+ # various tools for manipulating hash-like classes.
26
+ module HashTools
27
+
28
+ # Determines if given object is hashable (i.e. object responds to hash methods).
29
+ #
30
+ # === Parameters
31
+ # @param [Object] object to be tested
32
+ #
33
+ # === Return
34
+ # @return [TrueClass|FalseClass] true if object is hashable, false otherwise
35
+ def self.hashable?(object)
36
+ # note that we could obviously be more critical here, but this test has always been
37
+ # sufficient and excludes Arrays and Strings which respond to [] but not has_key?
38
+ #
39
+ # what we specifically don't want to do is .kind_of?(Hash) because that excludes the
40
+ # range of hash-like classes (such as Chef::Node::Attribute)
41
+ object.respond_to?(:has_key?)
42
+ end
43
+
44
+ # Determines if given class is hash-like (i.e. instance of class responds to hash methods).
45
+ #
46
+ # === Parameters
47
+ # @param [Class] clazz to be tested
48
+ #
49
+ # === Return
50
+ # @return [TrueClass|FalseClass] true if clazz is hash-like, false otherwise
51
+ def self.hash_like?(clazz)
52
+ clazz.public_instance_methods.include?('has_key?')
53
+ end
54
+
55
+ # Gets a value from a (deep) hash using a path given as an array of keys.
56
+ #
57
+ # === Parameters
58
+ # @param [Hash] hash for lookup or nil or empty
59
+ # @param [Array] path to existing value as array of keys or nil or empty
60
+ #
61
+ # === Return
62
+ # @return [Object] value or nil
63
+ def self.deep_get(hash, path)
64
+ hash ||= {}
65
+ path ||= []
66
+ last_index = path.size - 1
67
+ path.each_with_index do |key, index|
68
+ value = hash[key]
69
+ if index == last_index
70
+ return value
71
+ elsif hashable?(value)
72
+ hash = value
73
+ else
74
+ break
75
+ end
76
+ end
77
+ nil
78
+ end
79
+
80
+ # Set a given value on a (deep) hash using a path given as an array of keys.
81
+ #
82
+ # === Parameters
83
+ # @param [Hash] hash for insertion
84
+ # @param [Array] path to new value as array of keys
85
+ # @param [Object] value to insert
86
+ # @param [Class] clazz to allocate as needed when building deep hash or nil to infer from hash argument
87
+ #
88
+ # === Return
89
+ # @return [TrueClass] always true
90
+ def self.deep_set!(hash, path, value, clazz=nil)
91
+ raise ArgumentError.new("hash is invalid") unless hashable?(hash)
92
+ raise ArgumentError.new("path is invalid") if path.empty?
93
+ clazz ||= hash.class
94
+ raise ArgumentError.new("clazz is invalid") unless hash_like?(clazz)
95
+ last_index = path.size - 1
96
+ path.each_with_index do |key, index|
97
+ if index == last_index
98
+ hash[key] = value
99
+ else
100
+ subhash = hash[key]
101
+ unless hashable?(subhash)
102
+ subhash = clazz.new
103
+ hash[key] = subhash
104
+ end
105
+ hash = subhash
106
+ end
107
+ end
108
+ true
109
+ end
110
+
111
+ # Creates a deep clone of the given hash.
112
+ #
113
+ # note that not all objects are clonable in Ruby even though all respond to clone
114
+ # (which is completely counter-intuitive and contrary to all other managed languages).
115
+ # Java, for example, has the built-in Cloneable marker interface which we will simulate
116
+ # here with .duplicable? (because cloneable isn't actually a word) in case you need
117
+ # non-hash values to be deep-cloned by this method.
118
+ #
119
+ # also note that .duplicable? may imply caling .dup instead of .clone but developers tend
120
+ # to override .clone and totally forget to override .dup (and having two names for the
121
+ # same method is, yes, a bad idea in any language).
122
+ #
123
+ # === Parameters
124
+ # @param [Hash] original hash to clone
125
+ #
126
+ # === Return
127
+ # @return [Hash] deep cloned hash
128
+ def self.deep_clone(original)
129
+ result = original.clone
130
+ result.each do |k, v|
131
+ if hashable?(v)
132
+ result[k] = deep_clone(v)
133
+ elsif v.respond_to?(:duplicable?)
134
+ result[k] = (v.duplicable? ? v.clone : v)
135
+ else
136
+ result[k] = v
137
+ end
138
+ end
139
+ result
140
+ end
141
+
142
+ # Performs a deep merge (but not a deep clone) of one hash into another.
143
+ #
144
+ # === Parameters
145
+ # @param [Hash] target hash to contain original and merged data
146
+ # @param [Hash] source hash containing data to recursively assign or nil or empty
147
+ #
148
+ # === Return
149
+ # @return [Hash] to_hash result of merge
150
+ def self.deep_merge!(target, source)
151
+ source.each do |k, v|
152
+ if hashable?(target[k]) && hashable?(v)
153
+ deep_merge!(target[k], v)
154
+ else
155
+ target[k] = v
156
+ end
157
+ end if source
158
+ target
159
+ end
160
+
161
+ # Remove recursively values that exist equivalently in the match hash.
162
+ #
163
+ # === Parameters
164
+ # @param [Hash] target hash from which to remove matching values
165
+ # @param [Hash] source hash to compare against target for removal or nil or empty
166
+ #
167
+ # === Return
168
+ # @return [Hash] target hash with matching values removed, if any
169
+ def self.deep_remove!(target, source)
170
+ source.each do |k, v|
171
+ if target.has_key?(k)
172
+ if target[k] == v
173
+ target.delete(k)
174
+ elsif hashable?(v) && hashable?(target[k])
175
+ deep_remove!(target[k], v)
176
+ end
177
+ end
178
+ end if source
179
+ target
180
+ end
181
+
182
+ # Produce a difference from two hashes. The difference excludes any values which are
183
+ # common to both hashes.
184
+ #
185
+ # The result is a hash with the following keys:
186
+ # - :diff = hash with key common to both input hashes and value composed of the corresponding different values: { :left => <left value>, :right => <right value> }
187
+ # - :left_only = hash composed of items only found in left hash
188
+ # - :right_only = hash composed of items only found in right hash
189
+ #
190
+ # === Parameters
191
+ # @param [Hash] left side
192
+ # @param [Hash] right side
193
+ #
194
+ # === Return
195
+ # @return [Hash] result as hash of diffage
196
+ def self.deep_create_patch(left, right)
197
+ result = { :diff=>{}, :left_only=>{}, :right_only=>{} }
198
+ right.each do |k, v|
199
+ if left.include?(k)
200
+ if hashable?(v) && hashable?(left[k])
201
+ subdiff = deep_create_patch(left[k], v)
202
+ result[:right_only].merge!(k=>subdiff[:right_only]) unless subdiff[:right_only].empty?
203
+ result[:left_only].merge!(k=>subdiff[:left_only]) unless subdiff[:left_only].empty?
204
+ result[:diff].merge!(k=>subdiff[:diff]) unless subdiff[:diff].empty?
205
+ elsif v != left[k]
206
+ result[:diff].merge!(k=>{:left => left[k], :right=>v})
207
+ end
208
+ else
209
+ result[:right_only].merge!({ k => v })
210
+ end
211
+ end
212
+ left.each { |k, v| result[:left_only].merge!({ k => v }) unless right.include?(k) }
213
+ result
214
+ end
215
+
216
+ # Perform 3-way merge using given target and a patch hash (generated by deep_create_patch, etc.).
217
+ # values in target whose keys are in :left_only component of patch are removed
218
+ # values in :right_only component of patch get deep merged into target
219
+ # values in target whose keys are in :diff component of patch and which are identical to left
220
+ # side of diff get overwritten with right side of patch
221
+ #
222
+ # === Parameters
223
+ # @param [Hash] target hash where patch will be applied.
224
+ # @param [Hash] patch hash containing changes to apply.
225
+ #
226
+ # === Return
227
+ # @param [Hash] target hash with patch applied.
228
+ def self.deep_apply_patch!(target, patch)
229
+ deep_remove!(target, patch[:left_only])
230
+ deep_merge!(target, patch[:right_only])
231
+ deep_apply_diff!(target, patch[:diff])
232
+ target
233
+ end
234
+
235
+ # Recursively apply diff portion of a patch (generated by deep_create_patch, etc.).
236
+ #
237
+ # === Parameters
238
+ # @param [Hash] target hash where diff will be applied.
239
+ # @param [Hash] diff hash containing changes to apply.
240
+ #
241
+ # === Return
242
+ # @param [Hash] target hash with diff applied.
243
+ def self.deep_apply_diff!(target, diff)
244
+ diff.each do |k, v|
245
+ if v[:left] && v[:right]
246
+ target[k] = v[:right] if v[:left] == target[k]
247
+ elsif target.has_key?(k)
248
+ deep_apply_diff!(target[k], v)
249
+ end
250
+ end
251
+ target
252
+ end
253
+
254
+ end
255
+ end
@@ -29,4 +29,5 @@ module RightSupport
29
29
  end
30
30
  end
31
31
 
32
+ require 'right_support/data/hash_tools'
32
33
  require 'right_support/data/uuid'
@@ -7,7 +7,7 @@ spec = Gem::Specification.new do |s|
7
7
  s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
8
8
 
9
9
  s.name = 'right_support'
10
- s.version = '2.2.2'
10
+ s.version = '2.3.0'
11
11
  s.date = '2012-06-27'
12
12
 
13
13
  s.authors = ['Tony Spataro', 'Sergey Sergyenko', 'Ryan Williamson', 'Lee Kirchhoff', 'Sergey Enin']
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 2
9
- - 2
10
- version: 2.2.2
8
+ - 3
9
+ - 0
10
+ version: 2.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tony Spataro
@@ -42,6 +42,7 @@ files:
42
42
  - lib/right_support/crypto.rb
43
43
  - lib/right_support/crypto/signed_hash.rb
44
44
  - lib/right_support/data.rb
45
+ - lib/right_support/data/hash_tools.rb
45
46
  - lib/right_support/data/uuid.rb
46
47
  - lib/right_support/db.rb
47
48
  - lib/right_support/db/cassandra_model.rb