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.
@@ -1,232 +1,96 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'hashdiff'
3
+ require_relative 'parser'
4
4
 
5
5
  module AbideDevUtils
6
6
  module XCCDF
7
7
  # Contains methods and classes used to diff XCCDF-derived objects.
8
8
  module Diff
9
- DEFAULT_DIFF_OPTS = {
10
- similarity: 1,
11
- strict: true,
12
- strip: true,
13
- array_path: true,
14
- delimiter: '//',
15
- use_lcs: true,
16
- }.freeze
9
+ def self.benchmark_diff(xml1, xml2, opts = {})
10
+ bd = BenchmarkDiff.new(xml1, xml2, opts)
11
+ if opts[:raw]
12
+ return bd.diff_rules_raw if opts[:rules_only]
17
13
 
18
- # Represents a change in a diff.
19
- class ChangeSet
20
- attr_reader :change, :key, :value, :value_to
14
+ bd.diff_raw
15
+ else
16
+ return bd.diff_rules if opts[:rules_only]
21
17
 
22
- def initialize(change:, key:, value:, value_to: nil)
23
- validate_change(change)
24
- @change = change
25
- @key = key
26
- @value = value
27
- @value_to = value_to
28
- end
29
-
30
- def to_s
31
- value_change_string(value, value_to)
32
- end
33
-
34
- def to_h
35
- {
36
- change: change,
37
- key: key,
38
- value: value,
39
- value_to: value_to,
40
- }
41
- end
42
-
43
- def can_merge?(other)
44
- return false unless (change == '-' && other.change == '+') || (change == '+' && other.change == '-')
45
- return false unless key == other.key || value_hash_equality(other)
46
-
47
- true
48
- end
49
-
50
- def merge(other)
51
- unless can_merge?(other)
52
- raise ArgumentError, 'Cannot merge. Possible causes: change is identical; key or value do not match'
53
- end
54
-
55
- new_to_value = value == other.value ? nil : other.value
56
- ChangeSet.new(
57
- change: '~',
58
- key: key,
59
- value: value,
60
- value_to: new_to_value
61
- )
62
- end
63
-
64
- def merge!(other)
65
- new_props = merge(other)
66
- @change = new_props.change
67
- @key = new_props.key
68
- @value = new_props.value
69
- @value_to = new_props.value_to
70
- end
71
-
72
- private
73
-
74
- def value_hash_equality(other)
75
- equality = false
76
- value.each do |k, v|
77
- equality = true if v == other.value[k]
78
- end
79
- equality
80
- end
81
-
82
- def validate_change(chng)
83
- raise ArgumentError, "Change type #{chng} in invalid" unless ['+', '-', '~'].include?(chng)
84
- end
85
-
86
- def change_string(chng)
87
- case chng
88
- when '-'
89
- 'Remove'
90
- when '+'
91
- 'Add'
92
- else
93
- 'Change'
94
- end
95
- end
96
-
97
- def value_change_string(value, value_to)
98
- change_str = []
99
- change_diff = Hashdiff.diff(value, value_to || {}, AbideDevUtils::XCCDF::Diff::DEFAULT_DIFF_OPTS)
100
- return if change_diff.empty?
101
- return value_change_string_single_type(change_diff, value) if all_single_change_type?(change_diff)
102
-
103
- change_diff.each do |chng|
104
- change_str << if chng.length == 4
105
- "#{change_string(chng[0])} #{chng[1][0]} \"#{chng[2]}\" to \"#{chng[3]}\""
106
- else
107
- "#{change_string(chng[0])} #{chng[1][0]} with value #{chng[2]}"
108
- end
109
- end
110
- change_str.join(', ')
18
+ bd.diff
111
19
  end
20
+ end
112
21
 
113
- def value_change_string_single_type(change_diff, value)
114
- "#{change_string(change_diff[0][0])} #{value[:number]} - #{value[:level]} - #{value[:title]}"
22
+ # Class for benchmark diffs
23
+ class BenchmarkDiff
24
+ attr_reader :this, :other, :opts
25
+
26
+ # @param xml1 [String] path to the first benchmark XCCDF xml file
27
+ # @param xml2 [String] path to the second benchmark XCCDF xml file
28
+ # @param opts [Hash] options hash
29
+ def initialize(xml1, xml2, opts = {})
30
+ @this = new_benchmark(xml1)
31
+ @other = new_benchmark(xml2)
32
+ @opts = opts
115
33
  end
116
34
 
117
- def all_single_change_type?(change_diff)
118
- change_diff.length > 1 && change_diff.map { |x| x[0] }.uniq.length == 1
35
+ def diff_raw
36
+ @diff_raw ||= @this.diff(@other)
119
37
  end
120
- end
121
38
 
122
- # Class used to diff two Benchmark profiles.
123
- class ProfileDiff
124
- def initialize(profile_a, profile_b, opts = {})
125
- @profile_a = profile_a
126
- @profile_b = profile_b
127
- @opts = opts
39
+ def diff
40
+ warn 'Full benchmark diff is not yet implemented, return rules diff for now'
41
+ diff_rules
128
42
  end
129
43
 
130
- def diff
131
- @diff ||= new_diff
44
+ def diff_rules_raw
45
+ @diff_rules_raw ||= @this.diff_only_rules(@other)
132
46
  end
133
47
 
134
- private
48
+ def diff_rules
49
+ return diff_rules_raw if opts[:raw]
50
+ return [] if diff_rules_raw.all? { |r| r.type == :equal }
135
51
 
136
- def new_diff
137
- Hashdiff.diff(@profile_a, @profile_b, AbideDevUtils::XCCDF::Diff::DEFAULT_DIFF_OPTS).each_with_object({}) do |change, diff|
138
- val_to = change.length == 4 ? change[3] : nil
139
- change_key = change[2].is_a?(Hash) ? change[2][:title] : change[2]
140
- if diff.key?(change_key)
141
- diff[change_key] = merge_changes(
142
- [
143
- diff[change_key][0],
144
- ChangeSet.new(change: change[0], key: change[1], value: change[2], value_to: val_to),
145
- ]
146
- )
52
+ diff_hash = {
53
+ from: @this.to_s,
54
+ to: @other.to_s,
55
+ rules: {}
56
+ }
57
+ diff_rules_raw.each do |rule|
58
+ diff_hash[:rules][rule.type] ||= []
59
+ case rule.type
60
+ when :added
61
+ diff_hash[:rules][rule.type] << { number: rule.new_value.number.to_s, title: rule.new_value.title.to_s }
62
+ when :removed
63
+ diff_hash[:rules][rule.type] << { number: rule.old_value.number.to_s, title: rule.old_value.title.to_s }
147
64
  else
148
- diff[change_key] = [ChangeSet.new(change: change[0], key: change[1], value: change[2], value_to: val_to)]
65
+ rd_hash = {}
66
+ rd_hash[:from] = "#{rule.old_value&.number} #{rule.old_value&.title}" if rule.old_value
67
+ rd_hash[:to] = "#{rule.new_value&.number} #{rule.new_value&.title}" if rule.new_value
68
+ changes = rule.details.transform_values { |v| v.is_a?(Array) ? v.map(&:to_s) : v.to_s }
69
+ if opts[:ignore_changed_properties]
70
+ changes.delete_if { |k, _| opts[:ignore_changed_properties].include?(k.to_s) }
71
+ next if changes.empty? # Skip entirely if all changed filtered out
72
+ end
73
+ rd_hash[:changes] = changes unless changes.empty?
74
+ diff_hash[:rules][rule.type] << rd_hash
149
75
  end
150
76
  end
151
- end
152
-
153
- def merge_changes(changes)
154
- return changes if changes.length < 2
155
-
156
- if changes[0].can_merge?(changes[1])
157
- [changes[0].merge(changes[1])]
158
- else
159
- changes
77
+ unless opts[:no_stats]
78
+ stats_hash = {}
79
+ diff_hash[:rules].each do |type, rules|
80
+ stats_hash[type] = rules.size
81
+ end
82
+ diff_hash[:stats] = stats_hash unless stats_hash.empty?
160
83
  end
161
- end
162
- end
163
-
164
- # Class used to diff two AbideDevUtils::XCCDF::Benchmark objects.
165
- class BenchmarkDiff
166
- def initialize(benchmark_a, benchmark_b, opts = {})
167
- @benchmark_a = benchmark_a
168
- @benchmark_b = benchmark_b
169
- @opts = opts
170
- end
171
-
172
- def properties_to_diff
173
- @properties_to_diff ||= %i[title version profiles]
174
- end
175
-
176
- def title_version
177
- @title_version ||= diff_title_version
178
- end
179
-
180
- def profiles(profile: nil)
181
- @profiles ||= diff_profiles(profile: profile)
84
+ diff_hash
182
85
  end
183
86
 
184
87
  private
185
88
 
186
- def diff_title_version
187
- diff = Hashdiff.diff(
188
- @benchmark_a.to_h.reject { |k, _| k.to_s == 'profiles' },
189
- @benchmark_b.to_h.reject { |k, _| k.to_s == 'profiles' },
190
- AbideDevUtils::XCCDF::Diff::DEFAULT_DIFF_OPTS
191
- )
192
- diff.each_with_object({}) do |change, tdiff|
193
- val_to = change.length == 4 ? change[3] : nil
194
- change_key = change[2].is_a?(Hash) ? change[2][:title] : change[2]
195
- tdiff[change_key] = [] unless tdiff.key?(change_key)
196
- tdiff[change_key] << ChangeSet.new(change: change[0], key: change[1], value: change[2], value_to: val_to)
197
- end
89
+ # Returns a Benchmark object from a XCCDF xml file path
90
+ # @param xml [String] path to a XCCDF xml file
91
+ def new_benchmark(xml)
92
+ AbideDevUtils::XCCDF::Parser.parse(xml)
198
93
  end
199
-
200
- def diff_profiles(profile: nil)
201
- diff = {}
202
- other_hash = @benchmark_b.to_h[:profiles]
203
- @benchmark_a.to_h[:profiles].each do |name, data|
204
- next if profile && profile != name
205
-
206
- diff[name] = ProfileDiff.new(data, other_hash[name], @opts).diff
207
- end
208
- diff
209
- end
210
- end
211
-
212
- def self.diff_benchmarks(benchmark_a, benchmark_b, opts = {})
213
- profile = opts.fetch(:profile, nil)
214
- profile_key = profile.nil? ? 'all_profiles' : profile
215
- benchmark_diff = BenchmarkDiff.new(benchmark_a, benchmark_b, opts)
216
- transform_method_sym = opts.fetch(:raw, false) ? :to_h : :to_s
217
- diff = if profile.nil?
218
- benchmark_diff.profiles.each do |_, v|
219
- v.transform_values! { |x| x.map!(&transform_method_sym) }
220
- end
221
- else
222
- benchmark_diff.profiles(profile: profile)[profile].transform_values! { |x| x.map!(&transform_method_sym) }
223
- end
224
- return diff.values.flatten if opts.fetch(:raw, false)
225
-
226
- {
227
- 'benchmark' => benchmark_diff.title_version.transform_values { |x| x.map!(&:to_s) },
228
- profile_key => diff.values.flatten,
229
- }
230
94
  end
231
95
  end
232
96
  end
@@ -4,99 +4,6 @@ module AbideDevUtils
4
4
  module XCCDF
5
5
  module Parser
6
6
  module Helpers
7
- # Provides helper methods for working with XCCDF element children
8
- module ElementChildren
9
- def search_children
10
- @search_children ||= SearchChildren.new(children)
11
- end
12
-
13
- # Implements methods that allow for searching an XCCDF Element's children
14
- class SearchChildren
15
- attr_reader :children
16
-
17
- def initialize(children)
18
- @children = children
19
- end
20
-
21
- def recursive_select_children(children_to_search = children, &block)
22
- search_hits = []
23
- children_to_search.each do |child|
24
- found = yield child
25
- if found
26
- search_hits << child
27
- elsif child.respond_to?(:children)
28
- search_hits << recursive_select_children(child.children, &block)
29
- end
30
- end
31
- search_hits.flatten.compact.uniq
32
- end
33
-
34
- def recursive_find_child(children_to_search = children, &block)
35
- rescursive_select_children(children_to_search, &block).first
36
- end
37
-
38
- def find_children_that_respond_to(method, recurse: false)
39
- return recursive_select_children { |child| child.respond_to?(method) } if recurse
40
-
41
- children.select { |c| c.respond_to?(method.to_sym) }
42
- end
43
-
44
- def find_children_by_class(klass, recurse: false)
45
- return recursive_select_children { |child| child.instance_of?(klass) } if recurse
46
-
47
- children.select { |child| child.instance_of?(klass) }
48
- end
49
-
50
- def find_child_by_class(klass, recurse: false)
51
- return recursive_find_child { |child| child.is_a?(klass) } if recurse
52
-
53
- find_children_by_class(klass).first
54
- end
55
-
56
- def find_children_by_xpath(xpath, recurse: false)
57
- return recursive_select_children { |child| child.xpath == xpath } if recurse
58
-
59
- children.select { |child| child.xpath == xpath }
60
- end
61
-
62
- def find_child_by_xpath(xpath, recurse: false)
63
- return recursive_find_child { |child| child.xpath == xpath } if recurse
64
-
65
- find_children_by_xpath(xpath).first
66
- end
67
-
68
- def find_children_by_attribute(attribute, recurse: false)
69
- pr = proc do |child|
70
- next unless child.instance_of?(AbideDevUtils::XCCDF::Parser::Objects::AttributeValue)
71
-
72
- child.attribute == attribute
73
- end
74
- return recursive_select_children(&pr) if recurse
75
-
76
- children.select(&pr)
77
- end
78
-
79
- def find_child_by_attribute(attribute, recurse: false)
80
- find_children_by_attribute(attribute, recurse: recurse).first
81
- end
82
-
83
- def find_children_by_attribute_value(attribute, value, recurse: false)
84
- pr = proc do |child|
85
- next unless child.instance_of?(AbideDevUtils::XCCDF::Parser::Objects::AttributeValue)
86
-
87
- child.attribute == attribute && child.value == value
88
- end
89
- return recursive_select_children(&pr) if recurse
90
-
91
- children.select(&pr)
92
- end
93
-
94
- def find_child_by_attribute_value(attribute, value, recurse: false)
95
- find_children_by_attribute_value(attribute, value, recurse: recurse).first
96
- end
97
- end
98
- end
99
-
100
7
  # Provides helper methods for working with XML xpaths
101
8
  module XPath
102
9
  def find_element