abide_dev_utils 0.14.2 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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