right_support 2.2.2 → 2.3.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.
@@ -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