abide_dev_utils 0.14.1 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,347 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbideDevUtils
4
+ module XCCDF
5
+ module Parser
6
+ module Objects
7
+ # Methods for converting an object to a hash
8
+ module DiffableObject
9
+ # Generic error class for diffable objects
10
+ class DiffableObjectError < StandardError
11
+ def initialize(self_obj = nil, other_obj = nil, custom_msg: nil)
12
+ if custom_msg.nil?
13
+ super("This object (#{self_obj}) is not diffable with other object (#{other_obj})")
14
+ else
15
+ super(custom_msg)
16
+ end
17
+ @self_obj = self_obj
18
+ @other_obj = other_obj
19
+ @custom_msg = custom_msg
20
+ end
21
+ end
22
+
23
+ # Raised when the diffed objects are not instances of the same class
24
+ class NotSameClassError < DiffableObjectError
25
+ def initialize(self_obj = nil, other_obj = nil)
26
+ super(
27
+ self_obj,
28
+ other_obj,
29
+ custom_msg: "This object's class (#{self_obj.class}) does not match other object's class (#{other_obj.class})",
30
+ )
31
+ end
32
+ end
33
+
34
+ # Holds the result of a diff on a per-item basis.
35
+ class DiffChangeResult
36
+ attr_accessor :type, :details
37
+ attr_reader :old_value, :new_value, :element
38
+
39
+ def initialize(type, old_value, new_value, element, details = {})
40
+ @type = type
41
+ @old_value = old_value
42
+ @new_value = new_value
43
+ @element = element
44
+ @details = details
45
+ end
46
+
47
+ def to_h(hash_objs: false)
48
+ return new_hash(type, old_value, new_value, element, details) unless hash_objs
49
+
50
+ old_val = old_value.respond_to?(:to_s) ? old_value.to_s : old_value.inspect
51
+ new_val = new_value.respond_to?(:to_s) ? new_value.to_s : new_value.inspect
52
+ details_hash = details.transform_values do |val|
53
+ case val
54
+ when DiffChangeResult
55
+ val.to_h(hash_objs: hash_objs)
56
+ when Array
57
+ val.map { |v| v.is_a?(DiffChangeResult) ? v.to_h(hash_objs: hash_objs) : v.to_s }
58
+ else
59
+ val.to_s
60
+ end
61
+ end
62
+ new_hash(type, old_val, new_val, element, details_hash)
63
+ end
64
+
65
+ def to_yaml
66
+ require 'yaml'
67
+ to_h(hash_objs: true).to_yaml
68
+ end
69
+
70
+ def to_a
71
+ [type, old_value, new_value, element, details]
72
+ end
73
+
74
+ def to_s
75
+ "#{element} #{type}: #{old_value} -> #{new_value}, #{details}"
76
+ end
77
+
78
+ private
79
+
80
+ def new_hash(change_type, old_val, new_val, element, details_hash)
81
+ hsh = {}
82
+ hsh[:type] = change_type
83
+ hsh[:old_value] = old_val unless old_val.nil? || (old_val.respond_to?(:empty?) && old_val.empty?)
84
+ hsh[:new_value] = new_val unless new_val.nil? || (old_val.respond_to?(:empty?) && new_val.empty?)
85
+ hsh[:element] = element
86
+ hsh[:details] = details_hash unless details_hash.empty?
87
+ hsh
88
+ end
89
+ end
90
+
91
+ def diff_change_result(type, old_value, new_value, element = nil, details = {})
92
+ element = if element.nil?
93
+ get_element_from = old_value || new_value
94
+ get_element_from.respond_to?(:label) ? get_element_from.label.to_sym : :plain_object
95
+ else
96
+ element
97
+ end
98
+ DiffChangeResult.new(type, old_value, new_value, element, details)
99
+ end
100
+
101
+ def check_diffable!(other)
102
+ raise DiffableObjectError.new(self, other) unless other.class.included_modules.include?(DiffableObject)
103
+ raise NotSameClassError.new(self, other) unless other.instance_of?(self.class)
104
+ end
105
+
106
+ # Diff two parser objects
107
+ # @param other [Object] The object to compare to. Must be an instance
108
+ # of the same class as the object being diffed.
109
+ # @return [DiffChangeResult] The result of the diff
110
+ def diff(other)
111
+ return if other.nil?
112
+
113
+ check_diffable!(other)
114
+ case self
115
+ when AbideDevUtils::XCCDF::Parser::Objects::AttributeValue,
116
+ AbideDevUtils::XCCDF::Parser::Objects::ControlURI,
117
+ AbideDevUtils::XCCDF::Parser::Objects::Description,
118
+ AbideDevUtils::XCCDF::Parser::Objects::Fixtext,
119
+ AbideDevUtils::XCCDF::Parser::Objects::LongText,
120
+ AbideDevUtils::XCCDF::Parser::Objects::Platform,
121
+ AbideDevUtils::XCCDF::Parser::Objects::Rationale,
122
+ AbideDevUtils::XCCDF::Parser::Objects::ShortText,
123
+ AbideDevUtils::XCCDF::Parser::Objects::System,
124
+ AbideDevUtils::XCCDF::Parser::Objects::Title,
125
+ AbideDevUtils::XCCDF::Parser::Objects::Version
126
+ diff_str_obj(self, other)
127
+ when AbideDevUtils::XCCDF::Parser::Objects::Rule
128
+ diff_rule(self, other)
129
+ when AbideDevUtils::XCCDF::Parser::Objects::Check
130
+ diff_check(self, other)
131
+ when AbideDevUtils::XCCDF::Parser::Objects::ComplexCheck
132
+ diff_complex_check(self, other)
133
+ when AbideDevUtils::XCCDF::Parser::Objects::XccdfSelect
134
+ diff_xccdf_select(self, other)
135
+ when AbideDevUtils::XCCDF::Parser::Objects::Value
136
+ diff_value(self, other)
137
+ when AbideDevUtils::XCCDF::Parser::Objects::Group
138
+ diff_group(self, other)
139
+ when AbideDevUtils::XCCDF::Parser::Objects::Profile
140
+ diff_profile(self, other)
141
+ when AbideDevUtils::XCCDF::Parser::Objects::Benchmark
142
+ diff_benchmark(self, other)
143
+ else
144
+ diff_ambiguous(self, other, called_from_diff: true)
145
+ end
146
+ end
147
+
148
+ def diff_ambiguous(self_obj, other_obj, called_from_diff: false)
149
+ return if other_obj.nil?
150
+
151
+ if self_obj.is_a?(Array) && other_obj.is_a?(Array)
152
+ diff_array_obj(self_obj, other_obj)
153
+ elsif self_obj.respond_to?(:diff) && other_obj.respond_to?(:diff) && !called_from_diff
154
+ self_obj.diff(other_obj)
155
+ elsif self_obj.respond_to?(:to_s) && other_obj.respond_to?(:to_s)
156
+ diff_str_obj(self_obj, other_obj)
157
+ else
158
+ diff_plain_obj(self_obj, other_obj)
159
+ end
160
+ end
161
+
162
+ def diff_plain_obj(self_obj, other_obj)
163
+ result = self_obj == other_obj ? :equal : :not_equal
164
+ d_hash = {
165
+ self_ivars: self_obj.iv_to_h,
166
+ other_ivars: other_obj.iv_to_h,
167
+ }
168
+ diff_change_result(result, self_obj, other_obj, d_hash)
169
+ end
170
+
171
+ def diff_str_obj(self_obj, other_obj)
172
+ result = self_obj.to_s == other_obj.to_s ? :equal : :not_equal
173
+ diff_change_result(result, self_obj, other_obj)
174
+ end
175
+
176
+ def diff_array_obj(self_ary, other_ary)
177
+ sorted_self = self_ary.sort
178
+ sorted_other = other_ary.sort
179
+ added_ary = (sorted_other - sorted_self).map { |i| diff_change_result(:added, nil, i) }
180
+ removed_ary = (sorted_self - sorted_other).map { |i| diff_change_result(:removed, i, nil) }
181
+ changed_ary = correlate_added_removed(added_ary, removed_ary)
182
+ diffable_self = sorted_self - (changed_ary.map(&:old_value) + changed_ary.map(&:new_value)).compact
183
+ diffable_other = sorted_other - (changed_ary.map(&:old_value) + changed_ary.map(&:new_value)).compact
184
+ diff_ary = diffable_self.zip(diffable_other).filter_map do |(self_obj, other_obj)|
185
+ change = diff_ambiguous(self_obj, other_obj)
186
+ if change.type == :equal
187
+ nil
188
+ else
189
+ change
190
+ end
191
+ end
192
+ diff_ary + changed_ary
193
+ end
194
+
195
+ def correlate_added_removed(added_ary, removed_ary)
196
+ return [] if added_ary.empty? && removed_ary.empty?
197
+
198
+ actual_added = added_ary.dup
199
+ actual_removed = removed_ary.dup
200
+ correlated = added_ary.each_with_object([]) do |added, ary|
201
+ similarity = nil
202
+ removed = removed_ary.find do |r|
203
+ similarity = added.new_value.find_similarity(r.old_value)
204
+ similarity.any? { |s| s[3] }
205
+ end
206
+ next if removed.nil?
207
+
208
+ details_hash = {}
209
+ similarity.each do |similar|
210
+ details_hash[similar[0]] = similar[1..2] unless similar[3]
211
+ end
212
+ ary << diff_change_result(:not_equal, removed.old_value, added.new_value, nil, details_hash)
213
+ actual_added.delete(added)
214
+ actual_removed.delete(removed)
215
+ end
216
+ (correlated + actual_added + actual_removed).uniq
217
+ end
218
+
219
+ def diff_rule(self_rule, other_rule, diff_properties: %i[number title])
220
+ d_hash = diff_properties.each_with_object({}) do |prop, hsh|
221
+ hsh[prop] = diff_ambiguous(self_rule.send(prop), other_rule.send(prop))
222
+ end
223
+ result = result_from_details_hash(d_hash)
224
+ d_hash = process_details_hash!(d_hash)
225
+ diff_change_result(result, self_rule, other_rule, :rule, d_hash)
226
+ end
227
+
228
+ def diff_check(self_check, other_check)
229
+ d_hash = {
230
+ system: self_check.system.diff(other_check.system),
231
+ check_export: diff_ambiguous(self_check.check_export, other_check.check_export),
232
+ check_content_ref: diff_ambiguous(self_check.check_content_ref, other_check.check_content_ref),
233
+ }
234
+ result = result_from_details_hash(d_hash)
235
+ d_hash = process_details_hash!(d_hash)
236
+ diff_change_result(result, self_check, other_check, :check, d_hash)
237
+ end
238
+
239
+ def diff_complex_check(self_complex_check, other_complex_check)
240
+ d_hash = {
241
+ operator: self_complex_check.operator.diff(other_complex_check.operator),
242
+ check: diff_array_obj(self_complex_check.check, other_complex_check.check),
243
+ }
244
+ result = result_from_details_hash(d_hash)
245
+ d_hash = process_details_hash!(d_hash)
246
+ diff_change_result(result, self_complex_check, other_complex_check, :complex_check, d_hash)
247
+ end
248
+
249
+ def diff_xccdf_select(self_select, other_select)
250
+ d_hash = {
251
+ idref: diff_str_obj(self_select.to_s, other_select.to_s),
252
+ selected: diff_str_obj(self_select.selected, other_select.selected),
253
+ }
254
+ result = result_from_details_hash(d_hash)
255
+ d_hash = process_details_hash!(d_hash)
256
+ diff_change_result(result, self_select, other_select, :xccdf_select, d_hash)
257
+ end
258
+
259
+ def diff_value(self_value, other_value)
260
+ d_hash = {
261
+ title: self_value.title.diff(other_value.title),
262
+ description: self_value.description.diff(other_value.description),
263
+ text: diff_str_obj(self_value.text.to_s, other_value.text.to_s),
264
+ operator: self_value.operator.diff(other_value.operator),
265
+ type: self_value.type.diff(other_value.type),
266
+ }
267
+ result = result_from_details_hash(d_hash)
268
+ d_hash = process_details_hash!(d_hash)
269
+ diff_change_result(result, self_value, other_value, :value, d_hash)
270
+ end
271
+
272
+ def diff_group(self_group, other_group)
273
+ d_hash = {
274
+ title: self_group.title.diff(other_group.title),
275
+ description: self_group.description.diff(other_group.description),
276
+ }
277
+ if self_group.respond_to?(:group) && other_group.respond_to?(:group)
278
+ d_hash[:group] ||= []
279
+ g_diff = diff_ambiguous(self_group.group, other_group.group)
280
+ if g_diff.is_a?(Array)
281
+ d_hash[:group] += g_diff
282
+ else
283
+ d_hash[:group] << g_diff
284
+ end
285
+ end
286
+ if self_group.respond_to?(:rule) && other_group.respond_to?(:rule)
287
+ d_hash[:rule] ||= []
288
+ r_diff = diff_ambiguous(self_group.rule, other_group.rule)
289
+ if r_diff.is_a?(Array)
290
+ d_hash[:rule] += r_diff
291
+ else
292
+ d_hash[:rule] << r_diff
293
+ end
294
+ end
295
+ result = result_from_details_hash(d_hash)
296
+ d_hash = process_details_hash!(d_hash)
297
+ diff_change_result(result, self_group, other_group, :group, d_hash)
298
+ end
299
+
300
+ def diff_profile(self_profile, other_profile)
301
+ d_hash = {
302
+ title: self_profile.title.diff(other_profile.title),
303
+ description: self_profile.description.diff(other_profile.description),
304
+ level: diff_str_obj(self_profile.level, other_profile.level),
305
+ xccdf_select: diff_ambiguous(self_profile.xccdf_select, other_profile.xccdf_select),
306
+ }
307
+ result = result_from_details_hash(d_hash)
308
+ d_hash = process_details_hash!(d_hash)
309
+ diff_change_result(result, self_profile, other_profile, :profile, d_hash)
310
+ end
311
+
312
+ def diff_benchmark(self_benchmark, other_benchmark)
313
+ d_hash = {}
314
+ d_hash[:title] = self_benchmark.title.diff(other_benchmark.title)
315
+ d_hash[:description] = self_benchmark.description.diff(other_benchmark.description)
316
+ d_hash[:platform] = self_benchmark.platform.diff(other_benchmark.platform)
317
+ d_hash[:profile] = diff_ambiguous(self_benchmark.profile, other_benchmark.profile)
318
+ d_hash[:group] = diff_ambiguous(self_benchmark.group, other_benchmark.group)
319
+ d_hash[:value] = diff_ambiguous(self_benchmark.value, other_benchmark.value)
320
+ result = result_from_details_hash(d_hash)
321
+ d_hash = process_details_hash!(d_hash)
322
+ diff_change_result(result, self_benchmark, other_benchmark, :benchmark, d_hash)
323
+ end
324
+
325
+ def process_details_hash!(d_hash, _element = :plain_object)
326
+ d_hash.reject! do |_, v|
327
+ v.nil? || (v.empty? if v.respond_to?(:empty?)) || (v.type == :equal if v.respond_to?(:type))
328
+ end
329
+ d_hash
330
+ end
331
+
332
+ def result_from_details_hash(d_hash)
333
+ changed_types = %i[not_equal added removed]
334
+ results = d_hash.values.find do |v|
335
+ if v.is_a?(Array)
336
+ v.map(&:type).any? { |i| changed_types.include?(i) }
337
+ elsif v.respond_to?(:type)
338
+ changed_types.include?(v&.type)
339
+ end
340
+ end
341
+ results.nil? ? :equal : :not_equal
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end
347
+ end