Class: OctocatalogDiff::CatalogDiff::Display::Text
- Inherits:
-
OctocatalogDiff::CatalogDiff::Display
- Object
- OctocatalogDiff::CatalogDiff::Display
- OctocatalogDiff::CatalogDiff::Display::Text
- Defined in:
- lib/octocatalog-diff/catalog-diff/display/text.rb
Overview
Display the output from a diff in text format. Uses the ‘diffy’ gem to provide diffs in blocks of text. Formats results in a logical Puppet output.
Constant Summary collapse
- SEPARATOR =
'*******************************************'.freeze
Class Method Summary collapse
-
._adjust_for_display_datatype(obj1, obj2, option, logger) ⇒ <String, String> or <?, ?>
Utility Method! Called by adjust_for_display_datatype_changes to compare an old value to a new value and adjust as appropriate.
-
.add_source_file_line_info(opts = {}) ⇒ Object
Generate info about the source of the change.
-
.add_trailing_newlines(string_1, string_2) ⇒ Array<String>
Add “n” to the end of both strings, only if both strings are lacking it.
-
.addition_only_no_truncation(depth, hash) ⇒ Array<String>
Special case: addition only, no truncation.
-
.adjust_for_display_datatype_changes(diff, option, logger = nil) ⇒ Object
Utility Method! Implement the –display-datatype-changes option by: - Removing string-equivalent differences when option == false - Updating display of string-equivalent differences when option == true.
-
.adjust_position_of_plus_minus(string_in) ⇒ String
Adjust the space after of the ‘-` / `+` in the diff for single line diffs.
-
.class_name_for_diffy(class_name) ⇒ String
Utility Method! Harmonize equivalent class names for comparison purposes.
-
.diff_at_depth(depth, old_obj, new_obj) ⇒ Object
Get the diff between two arbitrary objects.
-
.diff_two_hashes_with_diffy(opts = {}) ⇒ Array<String>
Get the diff of two hashes.
-
.diff_two_strings_with_diffy(string1, string2, depth) ⇒ Object
Get the diff of two long strings.
-
.display_added_item(opts = {}) ⇒ Array
Display an added item.
-
.display_changed_or_nested_item(opts = {}) ⇒ Array
Display a changed or nested item.
-
.display_removed_item(opts = {}) ⇒ Array
Display a removed item.
-
.generate(diff, options_in = {}, logger = nil) ⇒ Array<String>
Generate the text representation of the ‘diff’ suitable for rendering in a console or log.
-
.hash_diff(obj, depth, key_in, nested = false) ⇒ Object
Get the diff between two hashes.
-
.left_pad(spaces, text = '') ⇒ Object
Utility Method! Indent a given text string with a certain number of spaces.
-
.loc_string(loc, compilation_dir, logger) ⇒ String
Convert { file => …, line => … } to displayable string.
-
.make_trailing_whitespace_visible(string_in) ⇒ String
Convert trailing whitespace to underscore for display purposes.
-
.single_lines?(string_1, string_2) ⇒ Boolean
Determine if two incoming strings are single lines.
-
.stringify_for_diffy(obj) ⇒ String
Utility Method! Given an arbitrary object, convert it into a string for use by ‘diffy’.
-
.truncate_string(str, limit) ⇒ String
Limit length of a string.
Methods inherited from OctocatalogDiff::CatalogDiff::Display
header, output, parse_diff_array_into_categorized_hashes, simple_deep_merge!
Class Method Details
._adjust_for_display_datatype(obj1, obj2, option, logger) ⇒ <String, String> or <?, ?>
Utility Method! Called by adjust_for_display_datatype_changes to compare an old value to a new value and adjust as appropriate.
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 510 def self._adjust_for_display_datatype(obj1, obj2, option, logger) # If not string-equal, return to leave untouched return [obj1, obj2] unless obj1.to_s == obj2.to_s # Delete if option to display these is false return [nil, nil] unless option # Delete if both objects are nil return [nil, nil] if obj1.nil? && obj2.nil? # If one is nil and the other is the empty string... return ['undef', '""'] if obj1.nil? return ['""', 'undef'] if obj2.nil? # If one is an integer and the other is a string return [obj1, "\"#{obj2}\""] if obj1.is_a?(Integer) && obj2.is_a?(String) return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(Integer) # True and false return [obj1, "\"#{obj2}\""] if obj1.is_a?(TrueClass) && obj2.is_a?(String) return [obj1, "\"#{obj2}\""] if obj1.is_a?(FalseClass) && obj2.is_a?(String) return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(TrueClass) return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(FalseClass) # Unhandled case - warn about it and then return inputs untouched # Note: If you encounter this, please report it so we can add a handler. # :nocov: msg = "In _adjust_for_display_datatype, objects '#{obj1.inspect}' (#{obj1.class}) and"\ " '#{obj2.inspect}' (#{obj2.class}) have identical string representations but"\ ' formatting is not implemented to update display.' logger.warn(msg) if logger [obj1, obj2] # :nocov: end |
.add_source_file_line_info(opts = {}) ⇒ Object
Generate info about the source of the change. Pass in parameters as a hash with indicated names.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 201 def self.add_source_file_line_info(opts = {}) item = opts.fetch(:item) result = opts.fetch(:result) old_loc = opts[:old_loc] new_loc = opts[:new_loc] = opts.fetch(:options, {}) logger = opts[:logger] # Initialize any currently undefined settings empty_hash = { 'file' => nil, 'line' => nil } old_loc ||= empty_hash new_loc ||= empty_hash return if old_loc == empty_hash && new_loc == empty_hash # Convert old_loc and new_loc to strings old_loc_string = loc_string(old_loc, [:compilation_from_dir], logger) new_loc_string = loc_string(new_loc, [:compilation_to_dir], logger) # Debug log information and build up local_result with printable changes local_result = [] if old_loc == new_loc || new_loc == empty_hash || old_loc_string == new_loc_string logger.debug "#{item} @ #{old_loc_string || 'nil'}" if logger local_result << " #{old_loc_string}".cyan unless old_loc_string.nil? elsif old_loc == empty_hash logger.debug "#{item} @ #{new_loc_string || 'nil'}" if logger local_result << " #{new_loc_string}".cyan unless new_loc_string.nil? else logger.debug "#{item} -@ #{old_loc_string} +@ #{new_loc_string}" if logger local_result << "- #{old_loc_string}".cyan local_result << "+ #{new_loc_string}".cyan end # Only modify result if option to display source file and line is enabled result.concat local_result if [:display_source_file_line] end |
.add_trailing_newlines(string_1, string_2) ⇒ Array<String>
Add “n” to the end of both strings, only if both strings are lacking it. This prevents “\ No newline at end of file” for single string comparison.
291 292 293 294 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 291 def self.add_trailing_newlines(string_1, string_2) return [string_1, string_2] unless string_1 !~ /\n\Z/ && string_2 !~ /\n\Z/ [string_1 + "\n", string_2 + "\n"] end |
.addition_only_no_truncation(depth, hash) ⇒ Array<String>
Special case: addition only, no truncation
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 365 def self.addition_only_no_truncation(depth, hash) result = [] # Single line strings hash.keys.sort.map do |key| next if hash[key] =~ /\n/ result << left_pad(2 * depth + 4, [key.inspect, ': ', hash[key].inspect].join('')).green end # Multi-line strings hash.keys.sort.map do |key| next if hash[key] !~ /\n/ result << left_pad(2 * depth + 4, [key.inspect, ': >>>'].join('')).green result.concat hash[key].split(/\n/).map(&:green) result << '<<<'.green end result end |
.adjust_for_display_datatype_changes(diff, option, logger = nil) ⇒ Object
Utility Method! Implement the –display-datatype-changes option by:
-
Removing string-equivalent differences when option == false
-
Updating display of string-equivalent differences when option == true
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 471 def self.adjust_for_display_datatype_changes(diff, option, logger = nil) diff.map! do |diff_obj| if diff_obj[0] == '+' || diff_obj[0] == '-' diff_obj[2] = 'undef' if diff_obj[2].nil? diff_obj else x2, x3 = _adjust_for_display_datatype(diff_obj[2], diff_obj[3], option, logger) if x2.nil? && x3.nil? # Delete this! Return nil and compact! will get rid of them. msg = "Adjust display for #{diff_obj[1].gsub(/\f/, '::')}: " \ "#{diff_obj[2].inspect} != #{diff_obj[3].inspect} DELETED" logger.debug(msg) if logger nil elsif x2 == diff_obj[2] && x3 == diff_obj[3] # Neither object changed diff_obj else # Adjust the display and return modified object msg = "Adjust display for #{diff_obj[1].gsub(/\f/, '::')}: " \ "old=#{x2.inspect} new=#{x3.inspect} "\ "(extra debugging: #{diff_obj[2].inspect} -> #{x2}; "\ "#{diff_obj[3].inspect} -> #{x3})" logger.debug(msg) if logger diff_obj[2] = x2 diff_obj[3] = x3 diff_obj end end end diff.compact! end |
.adjust_position_of_plus_minus(string_in) ⇒ String
Adjust the space after of the ‘-` / `+` in the diff for single line diffs. Diffy prints diffs with no space between the `-` / `+` in the text, but for single lines it’s easier to read with that space added.
301 302 303 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 301 def self.adjust_position_of_plus_minus(string_in) string_in.sub(/\A(\e\[\d+m)?([\-\+])/, '\1\2 ') end |
.class_name_for_diffy(class_name) ⇒ String
Utility Method! Harmonize equivalent class names for comparison purposes.
446 447 448 449 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 446 def self.class_name_for_diffy(class_name) return 'Integer' if class_name == 'Fixnum' class_name end |
.diff_at_depth(depth, old_obj, new_obj) ⇒ Object
Get the diff between two arbitrary objects
425 426 427 428 429 430 431 432 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 425 def self.diff_at_depth(depth, old_obj, new_obj) old_s = old_obj.to_s new_s = new_obj.to_s result = [] result << left_pad(2 * depth + 2, "- #{old_s}").red unless old_s == '' result << left_pad(2 * depth + 2, "+ #{new_s}").green unless new_s == '' result end |
.diff_two_hashes_with_diffy(opts = {}) ⇒ Array<String>
Get the diff of two hashes. Call the ‘diffy’ gem for this.
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 332 def self.diff_two_hashes_with_diffy(opts = {}) depth = opts.fetch(:depth, 0) hash1 = opts.fetch(:hash1, {}) hash2 = opts.fetch(:hash2, {}) limit = opts[:limit] strip_diff = opts.fetch(:strip_diff, false) # Special case: addition only, no truncation return addition_only_no_truncation(depth, hash2) if hash1 == {} && limit.nil? json_old = stringify_for_diffy(hash1) json_new = stringify_for_diffy(hash2) # If stripping the diff, we need to make sure diffy does not colorize the output, so that # there are not color codes in the output to deal with. diff = if strip_diff Diffy::Diff.new(json_old, json_new, context: 0).to_s(:text).split("\n") else Diffy::Diff.new(json_old, json_new, context: 0).to_s.split("\n") end raise "Diffy diff empty for string: #{json_old}" if diff.empty? # This is the array that is returned diff.map do |x| x = x[2..-1] if strip_diff # Drop first 2 characters: '+ ', '- ', or ' ' truncate_string(left_pad(2 * depth + 2, x), limit) end end |
.diff_two_strings_with_diffy(string1, string2, depth) ⇒ Object
Get the diff of two long strings. Call the ‘diffy’ gem for this.
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 261 def self.diff_two_strings_with_diffy(string1, string2, depth) # Single line strings? if single_lines?(string1, string2) string1, string2 = add_trailing_newlines(string1, string2) diff = Diffy::Diff.new(string1, string2, context: 2, include_diff_info: false).to_s.split("\n") return diff.map { |x| left_pad(2 * depth + 2, make_trailing_whitespace_visible(adjust_position_of_plus_minus(x))) } end # Multiple line strings string1, string2 = add_trailing_newlines(string1, string2) diff = Diffy::Diff.new(string1, string2, context: 2, include_diff_info: true).to_s.split("\n") diff.shift # Remove first line of diff info (filename that makes no sense) diff.shift # Remove second line of diff info (filename that makes no sense) diff.map { |x| left_pad(2 * depth + 2, make_trailing_whitespace_visible(x)) } end |
.display_added_item(opts = {}) ⇒ Array
Display an added item
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 162 def self.display_added_item(opts = {}) item = opts.fetch(:item) new_loc = opts.fetch(:new_loc) diff = opts.fetch(:diff) = opts.fetch(:options) logger = opts[:logger] result = [] add_source_file_line_info(item: item, result: result, new_loc: new_loc, options: , logger: logger) if [:display_detail_add] && diff.key?('parameters') limit = .fetch(:truncate_details, true) ? 80 : nil result << "+ #{item} =>".green result << ' parameters =>'.green result.concat( diff_two_hashes_with_diffy( depth: 1, hash2: Hash[diff['parameters'].sort], # Should work with somewhat older rubies too limit: limit, strip_diff: true ).map(&:green) ) else result << "+ #{item}".green if diff.key?('parameters') && logger && ![:display_detail_add_notice_printed] logger.info 'Note: you can use --display-detail-add to view details of added resources' [:display_detail_add_notice_printed] = true end end result end |
.display_changed_or_nested_item(opts = {}) ⇒ Array
Display a changed or nested item
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 122 def self.display_changed_or_nested_item(opts = {}) item = opts.fetch(:item) old_loc = opts.fetch(:old_loc) new_loc = opts.fetch(:new_loc) diff = opts.fetch(:diff) = opts.fetch(:options) logger = opts[:logger] result = [] info_hash = { item: item, result: result, old_loc: old_loc, new_loc: new_loc, options: , logger: logger } add_source_file_line_info(info_hash) result << " #{item} =>" diff.keys.sort.each { |key| result.concat hash_diff(diff[key], 1, key, true) } result end |
.display_removed_item(opts = {}) ⇒ Array
Display a removed item
144 145 146 147 148 149 150 151 152 153 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 144 def self.display_removed_item(opts = {}) item = opts.fetch(:item) old_loc = opts.fetch(:old_loc) = opts.fetch(:options) logger = opts[:logger] result = [] add_source_file_line_info(item: item, result: result, old_loc: old_loc, options: , logger: logger) result << "- #{item}".red end |
.generate(diff, options_in = {}, logger = nil) ⇒ Array<String>
Generate the text representation of the ‘diff’ suitable for rendering in a console or log.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 27 def self.generate(diff, = {}, logger = nil) # Empty? return [] if diff.empty? # We may modify this for temporary local use, but don't want to pass these changes # back to the rest of the program. = .dup # Enable color support if requested... String.colors_enabled = .fetch(:color, true) previous_diffy_default_format = Diffy::Diff.default_format Diffy::Diff.default_format = .fetch(:color, true) ? :color : :text # Strip out differences or update display where string matches but data type differs. # For example, 28 (the integer) and "28" (the string) have identical string # representations, but are different data types. Same for nil vs. "". adjust_for_display_datatype_changes(diff, [:display_datatype_changes], logger) # Call the utility method to sort changes into their respective types only_in_new, only_in_old, changed = parse_diff_array_into_categorized_hashes(diff) sorted_list = only_in_old.keys | only_in_new.keys | changed.keys sorted_list.sort! unless logger.nil? logger.debug "Added resources: #{only_in_new.keys.count}" logger.debug "Removed resources: #{only_in_old.keys.count}" logger.debug "Changed resources: #{changed.keys.count}" end # Run through the list to build the result result = [] sorted_list.each do |item| # Print the header if needed unless [:header].nil? result << [:header] unless [:header].empty? result << SEPARATOR [:header] = nil end # A removed item appears only in the old hash. if only_in_old.key?(item) result.concat display_removed_item( item: item, old_loc: only_in_old[item][:loc], options: , logger: logger ) # An added item appears only in the new hash. elsif only_in_new.key?(item) result.concat display_added_item( item: item, new_loc: only_in_new[item][:loc], diff: only_in_new[item][:diff], options: , logger: logger ) # A change can appear either in the change hash, the nested hash, or both. # Therefore, changes and nested changes are combined for display. elsif changed.key?(item) result.concat display_changed_or_nested_item( item: item, old_loc: changed[item][:old_loc], new_loc: changed[item][:new_loc], diff: changed[item][:diff], options: , logger: logger ) # An unrecognized change throws an error. This indicates a bug. else # :nocov: raise "BUG (please report): Unable to determine diff type of item: #{item.inspect}" # :nocov: end result << SEPARATOR end # Reset the global color-related flags String.colors_enabled = false Diffy::Diff.default_format = previous_diffy_default_format # The end result end |
.hash_diff(obj, depth, key_in, nested = false) ⇒ Object
Get the diff between two hashes. This is recursive-aware.
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 398 def self.hash_diff(obj, depth, key_in, nested = false) result = [] result << left_pad(2 * depth, " #{key_in} =>") if obj.key?(:old) && obj.key?(:new) if nested && obj[:old].is_a?(Hash) && obj[:new].is_a?(Hash) # Nested hashes will be stringified and then use 'diffy' result.concat diff_two_hashes_with_diffy(depth: depth, hash1: obj[:old], hash2: obj[:new]) elsif obj[:old].is_a?(String) && obj[:new].is_a?(String) # Strings will use 'diffy' to mimic the output seen when using # "diff" on the command line. result.concat diff_two_strings_with_diffy(obj[:old], obj[:new], depth) else # Stuff we don't recognize will be converted to a string and printed # with '+' and '-' unless the object resolves to an empty string. result.concat diff_at_depth(depth, obj[:old], obj[:new]) end else obj.keys.sort.each { |key| result.concat hash_diff(obj[key], 1 + depth, key, nested) } end result end |
.left_pad(spaces, text = '') ⇒ Object
Utility Method! Indent a given text string with a certain number of spaces
438 439 440 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 438 def self.left_pad(spaces, text = '') [' ' * spaces, text].join('') end |
.loc_string(loc, compilation_dir, logger) ⇒ String
Convert { file => …, line => … } to displayable string
242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 242 def self.loc_string(loc, compilation_dir, logger) return nil if loc.nil? || !loc.is_a?(Hash) || loc['file'].nil? || loc['line'].nil? result = "#{loc['file']}:#{loc['line']}" if compilation_dir rex = Regexp.new('^' + Regexp.escape(compilation_dir + '/')) result_new = result.sub(rex, '') if result_new != result logger.debug "Removed compilation directory in #{result} -> #{result_new}" if logger result = result_new end end result end |
.make_trailing_whitespace_visible(string_in) ⇒ String
Convert trailing whitespace to underscore for display purposes. Also convert special whitespace (r, n, t, …) to character representation.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 309 def self.make_trailing_whitespace_visible(string_in) return string_in unless string_in =~ /\A((?:.|\n)*?)(\s+)(\e\[0m)?\Z/ beginning = Regexp.last_match(1) trailing_space = Regexp.last_match(2) end_escape = Regexp.last_match(3) # Trailing space adjustment for line endings trailing_space.gsub! "\n", '\n' trailing_space.gsub! "\r", '\r' trailing_space.gsub! "\t", '\t' trailing_space.gsub! "\f", '\f' trailing_space.tr! ' ', '_' [beginning, trailing_space, end_escape].join('') end |
.single_lines?(string_1, string_2) ⇒ Boolean
Determine if two incoming strings are single lines. Returns true if both incoming strings are single lines, false otherwise.
282 283 284 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 282 def self.single_lines?(string_1, string_2) string_1.strip !~ /\n/ && string_2.strip !~ /\n/ end |
.stringify_for_diffy(obj) ⇒ String
Utility Method! Given an arbitrary object, convert it into a string for use by ‘diffy’. This basically exists so we can do something prettier than just calling .inspect or .to_s on object types we anticipate seeing, while not failing entirely on other object types.
457 458 459 460 461 462 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 457 def self.stringify_for_diffy(obj) return JSON.pretty_generate(obj) if OctocatalogDiff::Util::Util.object_is_any_of?(obj, [Hash, Array]) return '""' if obj.is_a?(String) && obj == '' return obj if OctocatalogDiff::Util::Util.object_is_any_of?(obj, [String, Fixnum, Integer, Float]) "#{class_name_for_diffy(obj.class)}: #{obj.inspect}" end |
.truncate_string(str, limit) ⇒ String
Limit length of a string
389 390 391 392 |
# File 'lib/octocatalog-diff/catalog-diff/display/text.rb', line 389 def self.truncate_string(str, limit) return str if limit.nil? || str.length <= limit "#{str[0..limit]}..." end |