abide_dev_utils 0.14.2 → 0.15.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.
@@ -27,10 +27,10 @@ module AbideDevUtils
27
27
  def self.link_profile_rules(benchmark)
28
28
  return unless benchmark.respond_to?(:profile)
29
29
 
30
- rules = benchmark.find_children_by_class(AbideDevUtils::XCCDF::Parser::Objects::Rule, recurse: true)
30
+ rules = benchmark.descendants.select { |d| d.label == 'rule' }
31
31
  benchmark.profile.each do |profile|
32
32
  profile.xccdf_select.each do |sel|
33
- rules.select { |rule| rule.id == sel.idref }.each do |rule|
33
+ rules.select { |rule| rule.id.value == sel.idref.value }.each do |rule|
34
34
  rule.add_link(profile)
35
35
  profile.add_link(rule)
36
36
  end
@@ -41,14 +41,13 @@ module AbideDevUtils
41
41
  def self.link_rule_values(benchmark)
42
42
  return unless benchmark.respond_to?(:value)
43
43
 
44
- rules = benchmark.find_children_by_class(AbideDevUtils::XCCDF::Parser::Objects::Rule, recurse: true)
44
+ rules = benchmark.descendants.select { |d| d.label == 'rule' }
45
45
  benchmark.value.each do |value|
46
- rules.each do |rule|
47
- unless rule.find_children_by_attribute_value('value-id', value.id, recurse: true).empty?
48
- rule.add_link(value)
49
- value.add_link(rule)
50
- end
51
- end
46
+ rule = rules.find { |r| r.title.to_s == value.title.to_s }
47
+ next unless rule
48
+
49
+ rule.add_link(value)
50
+ value.add_link(rule)
52
51
  end
53
52
  end
54
53
  end
@@ -37,16 +37,7 @@ module AbideDevUtils
37
37
  # Diffs two xccdf files
38
38
  def self.diff(file1, file2, opts)
39
39
  require 'abide_dev_utils/xccdf/diff'
40
- bm1 = Benchmark.new(file1)
41
- bm2 = Benchmark.new(file2)
42
- AbideDevUtils::XCCDF::Diff.diff_benchmarks(bm1, bm2, opts)
43
- end
44
-
45
- # Use new-style diff
46
- def self.new_style_diff(file1, file2, opts)
47
- require 'abide_dev_utils/xccdf/diff/benchmark'
48
- bm_diff = AbideDevUtils::XCCDF::Diff::BenchmarkDiff.new(file1, file2, opts)
49
- bm_diff.diff
40
+ AbideDevUtils::XCCDF::Diff.benchmark_diff(file1, file2, opts)
50
41
  end
51
42
 
52
43
  # Common constants and methods included by nearly everything else
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abide_dev_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.2
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - abide-team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-28 00:00:00.000000000 Z
11
+ date: 2023-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -136,20 +136,6 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '1.0'
139
- - !ruby/object:Gem::Dependency
140
- name: amatch
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '0.4'
146
- type: :runtime
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '0.4'
153
139
  - !ruby/object:Gem::Dependency
154
140
  name: facterdb
155
141
  requirement: !ruby/object:Gem::Requirement
@@ -448,16 +434,10 @@ files:
448
434
  - lib/abide_dev_utils/version.rb
449
435
  - lib/abide_dev_utils/xccdf.rb
450
436
  - lib/abide_dev_utils/xccdf/diff.rb
451
- - lib/abide_dev_utils/xccdf/diff/benchmark.rb
452
- - lib/abide_dev_utils/xccdf/diff/benchmark/number_title.rb
453
- - lib/abide_dev_utils/xccdf/diff/benchmark/profile.rb
454
- - lib/abide_dev_utils/xccdf/diff/benchmark/property.rb
455
- - lib/abide_dev_utils/xccdf/diff/benchmark/property_existence.rb
456
- - lib/abide_dev_utils/xccdf/diff/utils.rb
457
437
  - lib/abide_dev_utils/xccdf/parser.rb
458
438
  - lib/abide_dev_utils/xccdf/parser/helpers.rb
459
439
  - lib/abide_dev_utils/xccdf/parser/objects.rb
460
- - lib/abide_dev_utils/xccdf/parser/objects/digest_object.rb
440
+ - lib/abide_dev_utils/xccdf/parser/objects/diffable_object.rb
461
441
  - lib/abide_dev_utils/xccdf/parser/objects/numbered_object.rb
462
442
  - lib/abide_dev_utils/xccdf/utils.rb
463
443
  - new_diff.rb
@@ -483,7 +463,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
483
463
  - !ruby/object:Gem::Version
484
464
  version: '0'
485
465
  requirements: []
486
- rubygems_version: 3.1.4
466
+ rubygems_version: 3.4.6
487
467
  signing_key:
488
468
  specification_version: 4
489
469
  summary: Helper utilities for developing compliance Puppet code
@@ -1,270 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'abide_dev_utils/xccdf/diff/benchmark/property_existence'
4
-
5
- module AbideDevUtils
6
- module XCCDF
7
- module Diff
8
- # Diffs two XCCDF benchmarks using the title / number of the items as the primary
9
- # diff properties.
10
- class NumberTitleDiff
11
- SKIP_DIFF_TYPES = %i[equal both].freeze
12
-
13
- def initialize(numbered_children, other_numbered_children)
14
- new_number_title_objs(numbered_children, other_numbered_children)
15
- end
16
-
17
- def diff
18
- @diff ||= find_diffs(@number_title_objs, @other_number_title_objs)
19
- end
20
-
21
- def to_s
22
- parts = []
23
- @diff.each do |_, diffs|
24
- diffs.each do |dh|
25
- parts << dh[:diff_text]
26
- end
27
- end
28
- parts.join("\n")
29
- end
30
-
31
- private
32
-
33
- attr_writer :diff
34
-
35
- def added_number_title_objs
36
- added_titles = @self_prop_checker.added_titles
37
- @other_number_title_objs.select do |nto|
38
- added_titles.include?(nto.title)
39
- end
40
- end
41
-
42
- def removed_number_title_objs
43
- removed_titles = @self_prop_checker.removed_titles
44
- @number_title_objs.select do |nto|
45
- removed_titles.include?(nto.title)
46
- end
47
- end
48
-
49
- def find_diffs(objs, other_objs)
50
- diffs = []
51
- added_number_title_objs.each do |nto|
52
- change_type = %i[both added]
53
- stand_in = NumberTitleContainerStandIn.new(change_type)
54
- diffs << process_diffs([diff_hash(change_type, stand_in, nto)])
55
- end
56
- removed_number_title_objs.each do |nto|
57
- change_type = %i[both removed]
58
- stand_in = NumberTitleContainerStandIn.new(change_type)
59
- diffs << process_diffs([diff_hash(change_type, nto, stand_in)])
60
- end
61
- objs.each do |obj|
62
- obj_diffs = other_objs.each_with_object([]) do |other_obj, o_ary|
63
- obj_diff = obj.diff(other_obj)
64
- next if SKIP_DIFF_TYPES.include?(obj_diff[0])
65
-
66
- o_ary << diff_hash(obj_diff, obj, other_obj)
67
- end
68
-
69
- processed_obj_diffs = process_diffs(obj_diffs)
70
- diffs << processed_obj_diffs unless obj_diffs.empty?
71
- end
72
- diffs
73
- end
74
-
75
- def process_diffs(diffs)
76
- return {} if diffs.empty?
77
-
78
- raise "Unexpected diffs: #{diffs}" if diffs.length > 2
79
-
80
- return diffs[0] if diffs.length == 1
81
-
82
- if diffs[0][:type][0] == PropChecker.inverse_existence_state[diffs[1][:type][0]]
83
- diffs[0]
84
- else
85
- diffs[1]
86
- end
87
- end
88
-
89
- def diff_hash(diff_type, obj, other_obj)
90
- {
91
- self: obj.child,
92
- other: other_obj.child,
93
- type: diff_type,
94
- text: diff_type_text(diff_type, obj, other_obj),
95
- number: obj.number,
96
- other_number: other_obj.number,
97
- title: obj.title,
98
- other_title: other_obj.title,
99
- }
100
- end
101
-
102
- def diff_type_text(diff_type, obj, other_obj)
103
- DiffTypeText.text(diff_type, obj, other_obj)
104
- end
105
-
106
- def new_number_title_objs(children, other_children)
107
- number_title_objs = children.map { |c| NumberTitleContainer.new(c) }.sort
108
- other_number_title_objs = other_children.map { |c| NumberTitleContainer.new(c) }.sort
109
- @self_prop_checker = PropChecker.new(number_title_objs, other_number_title_objs)
110
- @other_prop_checker = PropChecker.new(other_number_title_objs, number_title_objs)
111
- number_title_objs.map { |n| n.prop_checker = @self_prop_checker }
112
- other_number_title_objs.map { |n| n.prop_checker = @other_prop_checker }
113
- @number_title_objs = number_title_objs
114
- @other_number_title_objs = other_number_title_objs
115
- end
116
- end
117
-
118
- # Creates string representations of diff types
119
- class DiffTypeText
120
- def self.text(diff_type, obj, other_obj)
121
- case diff_type[0]
122
- when :equal
123
- 'The objects are equal'
124
- when :title
125
- "Title changed: Number \"#{obj.number}\": #{obj.title} -> #{other_obj.title}"
126
- when :number
127
- number_diff_type_text(diff_type, obj, other_obj)
128
- when :both
129
- both_diff_type_text(diff_type, obj, other_obj)
130
- when :add
131
- "Add object with number \"#{other_obj.number}\" and title \"#{other_obj.title}\""
132
- when :remove
133
- "Remove object with number \"#{obj.number}\" and title \"#{obj.title}\""
134
- else
135
- raise ArgumentError, "Unknown diff type: #{diff_type}"
136
- end
137
- end
138
-
139
- def self.number_diff_type_text(diff_type, obj, other_obj)
140
- case diff_type[1]
141
- when :added
142
- "Number changed (New Number): Title \"#{obj.title}\": #{obj.number} -> #{other_obj.number}"
143
- when :exists
144
- "Number changed (Existing Number): Title \"#{obj.title}\": #{obj.number} -> #{other_obj.number}"
145
- else
146
- raise ArgumentError, "Unknown diff type for number change: #{diff_type[1]}"
147
- end
148
- end
149
-
150
- def self.both_diff_type_text(diff_type, obj, other_obj)
151
- case diff_type[1]
152
- when :added
153
- "Added object: Title \"#{other_obj.title}\": Number \"#{other_obj.number}\""
154
- when :removed
155
- "Removed object: Title \"#{obj.title}\": Number \"#{obj.number}\""
156
- else
157
- raise ArgumentError, "Unknown diff type for both change: #{diff_type[1]}"
158
- end
159
- end
160
- end
161
-
162
- # Checks properties for existence in both benchmarks.
163
- class PropChecker < AbideDevUtils::XCCDF::Diff::PropertyExistenceChecker
164
- attr_reader :all_numbers, :all_titles, :other_all_numbers, :other_all_titles
165
-
166
- def initialize(number_title_objs, other_number_title_objs)
167
- super
168
- @all_numbers = number_title_objs.map(&:number)
169
- @all_titles = number_title_objs.map(&:title)
170
- @other_all_numbers = other_number_title_objs.map(&:number)
171
- @other_all_titles = other_number_title_objs.map(&:title)
172
- end
173
-
174
- def title(title)
175
- property_existence(title, @all_titles, @other_all_titles)
176
- end
177
-
178
- def number(number)
179
- property_existence(number, @all_numbers, @other_all_numbers)
180
- end
181
-
182
- def added_numbers
183
- added(@all_numbers, @other_all_numbers)
184
- end
185
-
186
- def removed_numbers
187
- removed(@all_numbers, @other_all_numbers)
188
- end
189
-
190
- def added_titles
191
- added(@all_titles, @other_all_titles)
192
- end
193
-
194
- def removed_titles
195
- removed(@all_titles, @other_all_titles)
196
- end
197
- end
198
-
199
- class NumberTitleDiffError < StandardError; end
200
- class InconsistentDiffTypeError < StandardError; end
201
-
202
- # Holds a number and title for a child of a benchmark
203
- # and provides methods to compare it to another child.
204
- class NumberTitleContainer
205
- include ::Comparable
206
- attr_accessor :prop_checker
207
- attr_reader :child, :number, :title
208
-
209
- def initialize(child, prop_checker = nil)
210
- @child = child
211
- @number = child.number.to_s
212
- @title = child.title.to_s
213
- @prop_checker = prop_checker
214
- end
215
-
216
- def diff(other)
217
- return %i[equal exist] if number == other.number && title == other.title
218
-
219
- if number == other.number && title != other.title
220
- c_diff = correlate_prop_diff_types(@prop_checker.title(other.title),
221
- other.prop_checker.title(other.title))
222
- [:title, c_diff]
223
- elsif title == other.title && number != other.number
224
- c_diff = correlate_prop_diff_types(@prop_checker.number(other.number),
225
- other.prop_checker.number(other.number))
226
- [:number, c_diff]
227
- else
228
- %i[both exist]
229
- end
230
- rescue StandardError => e
231
- err_str = [
232
- 'Error diffing number and title',
233
- "Number: #{number}",
234
- "Title: #{title}",
235
- "Other number: #{other.number}",
236
- "Other title: #{other.title}",
237
- e.message,
238
- ]
239
- raise NumberTitleDiffError, err_str.join(', ')
240
- end
241
-
242
- def <=>(other)
243
- child <=> other.child
244
- end
245
-
246
- private
247
-
248
- def correlate_prop_diff_types(self_type, other_type)
249
- inverse_diff_type = PropChecker.inverse_existence_state[self_type]
250
- return other_type if inverse_diff_type.nil?
251
-
252
- self_type
253
- end
254
- end
255
-
256
- # Stand-in object for a NumberTitleContainer when the NumberTitleContainer
257
- # would not exist. This is used when a change is an add or remove.
258
- class NumberTitleContainerStandIn
259
- attr_reader :child, :number, :title
260
-
261
- def initialize(change_type)
262
- @change_type = change_type
263
- @child = nil
264
- @number = ''
265
- @title = ''
266
- end
267
- end
268
- end
269
- end
270
- end
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'abide_dev_utils/xccdf/diff/benchmark/property_existence'
4
-
5
- module AbideDevUtils
6
- module XCCDF
7
- module Diff
8
- # Diffs two sets of XCCDF profiles.
9
- class ProfileDiff
10
- def initialize(profiles, other_profiles)
11
- new_profile_rule_objs(profiles, other_profiles)
12
- end
13
-
14
- def diff_hash(diff_type, profile1, prof1_rules, profile2, prof2_rules)
15
- {
16
-
17
- }
18
- end
19
-
20
- private
21
-
22
- def new_profile_rule_objs(profiles, other_profiles)
23
- profile_objs = containers_from_profile_list(profiles)
24
- other_profile_objs = containers_from_profile_list(other_profiles)
25
- @self_prop_checker = PropertyExistenceChecker.new(profile_objs, other_profile_objs)
26
- @other_prop_checker = PropertyExistenceChecker.new(other_profile_objs, profile_objs)
27
- profile_objs.map { |p| p.prop_checker = @self_prop_checker }
28
- other_profile_objs.map { |p| p.prop_checker = @other_prop_checker }
29
- @profile_rule_objs = profile_objs
30
- @other_profile_rule_objs = other_profile_objs
31
- end
32
-
33
- def containers_from_profile_list(profile_list)
34
- profile_list.each_with_object([]) do |profile, ary|
35
- ary << ProfileRuleContainer.new(profile)
36
- end
37
- end
38
- end
39
-
40
- # Checks property existence in both profiles.
41
- class PropChecker < AbideDevUtils::XCCDF::Diff::Benchmark::PropertyExistence
42
- def initialize(profile_rule_objs, other_profile_rule_objs)
43
- super
44
- @profile_rule_objs = profile_rule_objs
45
- @other_profile_rule_objs = other_profile_rule_objs
46
- @profiles = profile_rule_objs.map(&:profile)
47
- @other_profiles = other_profile_rule_objs.map(&:profile)
48
- end
49
-
50
- def profile(profile)
51
- profile_key = profile.respond_to?(:id) ? profile.id : profile
52
- property_existence(profile_key, @profiles, @other_profiles)
53
- end
54
-
55
- def rule_in_profile(rule, profile, rule_key: :title)
56
- rk = rule.respond_to?(rule_key) ? rule.send(rule_key) : rule
57
- rules = @profiles.find { |p| p.id == profile }.linked_rule.map(&rk)
58
- other_rules = @other_profiles.find { |p| p.id == profile }.linked_rule.map(&rk)
59
- property_existence(rk, rules, other_rules)
60
- end
61
-
62
- def added_profiles
63
- added(@other_profiles.map(&:id), @profiles.map(&:id))
64
- end
65
-
66
- def removed_profiles
67
- removed(@profiles.map(&:id), @other_profiles.map(&:id))
68
- end
69
-
70
- def added_rules_by_profile
71
- @rules_by_profile.each_with_object({}) do |(profile, rules), hsh|
72
- next unless @other_rules_by_profile.key?(profile)
73
-
74
- hsh[profile] = added(rules, @other_rules_by_profile[profile])
75
- end
76
- end
77
-
78
- def removed_rules_by_profile
79
- @rules_by_profile.each_with_object({}) do |(profile, rules), hsh|
80
- next unless @other_rules_by_profile.key?(profile)
81
-
82
- hsh[profile] = removed(rules, @other_rules_by_profile[profile])
83
- end
84
- end
85
- end
86
-
87
- class ProfileRuleContainer
88
- include ::Comparable
89
- attr_accessor :prop_checker
90
- attr_reader :profile, :rules
91
-
92
- def initialize(profile, prop_checker = nil)
93
- @profile = profile
94
- @rules = profile.linked_rule
95
- @prop_checker = prop_checker
96
- end
97
-
98
- def <=>(other)
99
- @profile.id <=> other.profile.id
100
- end
101
- end
102
- end
103
- end
104
- end
@@ -1,127 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'amatch'
4
-
5
- module AbideDevUtils
6
- module XCCDF
7
- module Diff
8
- # Diffs benchmark properties.
9
- module BenchmarkPropertyDiff
10
- DEFAULT_PROPERTY_DIFF_OPTS = {
11
- rule_properties_for_similarity: %i[title description rationale fixtext],
12
- rule_properties_for_confidence: %i[description rationale fixtext],
13
- rule_confidence_property_threshold: 0.7,
14
- rule_confidence_total_threshold: 0.5,
15
- digest_similarity_threshold: 0.75,
16
- digest_similarity_label_weights: {
17
- 'title' => 4.0,
18
- },
19
- digest_similarity_only_labels: %w[title description fixtext rationale],
20
- digest_top_x_similarities: 10,
21
- }.freeze
22
-
23
- def safe_rule_prop(rule, prop)
24
- rule.respond_to?(prop) ? rule.send(prop).to_s : :none
25
- end
26
-
27
- def self_rule_vals
28
- @self_rule_vals ||= {}
29
- end
30
-
31
- def other_rule_vals
32
- @other_rule_vals ||= {}
33
- end
34
-
35
- def add_rule_val(rule, prop, val, container: nil)
36
- raise ArgumentError, 'container must not be nil' if container.nil?
37
-
38
- return unless container.dig(rule, prop).nil?
39
-
40
- container[rule] ||= {}
41
- container[rule][prop] = val
42
- end
43
-
44
- def add_self_rule_val(rule, prop, val)
45
- add_rule_val(rule, prop, val, container: self_rule_vals)
46
- end
47
-
48
- def add_other_rule_val(rule, prop, val)
49
- add_rule_val(rule, prop, val, container: other_rule_vals)
50
- end
51
-
52
- def same_rule?(prop_similarities)
53
- confidence_indicator = 0.0
54
- opts[:rule_properties_for_confidence].each do |prop|
55
- confidence_indicator += 1.0 if prop_similarities[prop] >= opts[:rule_confidence_property_threshold]
56
- end
57
- (confidence_indicator / opts[:rule_properties_for_confidence].length) >= opts[:rule_confidence_total_threshold]
58
- end
59
-
60
- def maxed_digest_similarities(child, other_children)
61
- similarities = other_children.each_with_object([]) do |other_child, ary|
62
- if other_child.digest_equal? child
63
- ary << [1.0, other_child]
64
- next
65
- end
66
-
67
- d_sim = child.digest_similarity(other_child,
68
- only_labels: opts[:digest_similarity_only_labels],
69
- label_weights: opts[:digest_similarity_label_weights])
70
- ary << [d_sim, other_child]
71
- end
72
- max_digest_similarities(similarities)
73
- end
74
-
75
- def max_digest_similarities(digest_similarities)
76
- digest_similarities.reject! { |s| s[0] < opts[:digest_similarity_threshold] }
77
- return digest_similarities if digest_similarities.empty?
78
-
79
- digest_similarities.max_by(opts[:digest_top_x_similarities]) { |s| s[0] }
80
- end
81
-
82
- def rule_property_similarity(rule1, rule2)
83
- prop_similarities = {}
84
- prop_diff = {}
85
- opts[:rule_properties_for_similarity].each do |prop|
86
- add_self_rule_val(rule1, prop, safe_rule_prop(rule1, prop).to_s)
87
- add_other_rule_val(rule2, prop, safe_rule_prop(rule2, prop).to_s)
88
- prop_similarities[prop] = self_rule_vals[rule1][prop].levenshtein_similar(other_rule_vals[rule2][prop])
89
- if prop_similarities[prop] < 1.0
90
- prop_diff[prop] = { self: self_rule_vals[rule1][prop], other: other_rule_vals[rule2][prop] }
91
- end
92
- end
93
- total = prop_similarities.values.sum / opts[:rule_properties_for_similarity].length
94
- {
95
- total: total,
96
- prop_similarities: prop_similarities,
97
- prop_diff: prop_diff,
98
- confident_same: same_rule?(prop_similarities),
99
- }
100
- end
101
-
102
- def most_similar(child, maxed_digest_similarities)
103
- most_similar_map = maxed_digest_similarities.each_with_object({}) do |similarity, h|
104
- prop_similarities = rule_property_similarity(child, similarity[1])
105
- if child.title.to_s == similarity[1].title.to_s
106
- prop_similarities[:total] = 99.0 # magic number denoting a title match
107
- end
108
- h[prop_similarities[:total]] = { self: child, other: similarity[1] }.merge(prop_similarities)
109
- end
110
- most_similar_map[most_similar_map.keys.max]
111
- end
112
-
113
- def find_most_similar(children, other_children)
114
- children.each_with_object({}) do |benchmark_child, h|
115
- maxed_similarities = maxed_digest_similarities(benchmark_child, other_children)
116
- next if maxed_similarities.empty?
117
-
118
- best = most_similar(benchmark_child, maxed_similarities)
119
- next if best.nil? || best.empty?
120
-
121
- h[benchmark_child] = best
122
- end
123
- end
124
- end
125
- end
126
- end
127
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AbideDevUtils
4
- module XCCDF
5
- module Diff
6
- # PropertyExistenceChecker provides methods to check existence state of various properties
7
- class PropertyExistenceChecker
8
- def initialize(*_args); end
9
-
10
- # Compares two arrays (or other iterables implementing `#to_a`)
11
- # containing properies and returns an array of the properties
12
- # that are added by other_props but not in self_props.
13
- def added(self_props, other_props)
14
- other_props.to_a - self_props.to_a
15
- end
16
-
17
- # Compares two arrays (or other iterables implementing `#to_a`)
18
- # containing properies and returns an array of the properties
19
- # that are removed by other_props but exist in self_props.
20
- def removed(this, other)
21
- this.to_a - other.to_a
22
- end
23
-
24
- # Returns a hash of existence states and their inverse.
25
- def self.inverse_existence_state
26
- {
27
- removed: :added,
28
- added: :removed,
29
- exists: :exists,
30
- }
31
- end
32
-
33
- private
34
-
35
- def property_existence(property, self_props, other_props)
36
- if self_props.include?(property) && !other_props.include?(property)
37
- :removed
38
- elsif !self_props.include?(property) && other_props.include?(property)
39
- :added
40
- else
41
- :exists
42
- end
43
- end
44
- end
45
- end
46
- end
47
- end