Class: Refiner
Overview
Compute longest common substring based diff between two strings.
The diff format is first the old string:
-
in red
-
with each line prefixed with minuses
-
removed characters highlighted in inverse video
Then comes the new string:
-
in green
-
with each line prefixed with plusses
-
added characters highlighted in inverse video
Constant Summary collapse
- REFINEMENT_THRESHOLD =
If either old or new would get more than this percentage of chars highlighted, consider this to be a replacement rather than a change and just don’t highlight anything.
30
Constants included from Colors
Colors::BOLD, Colors::CYAN, Colors::ESC, Colors::GREEN, Colors::NOT_REVERSE, Colors::RED, Colors::RESET, Colors::REVERSE
Instance Attribute Summary collapse
-
#refined_new ⇒ Object
readonly
Returns the value of attribute refined_new.
-
#refined_old ⇒ Object
readonly
Returns the value of attribute refined_old.
Instance Method Summary collapse
- #censor_highlights(old, new, old_highlights, new_highlights) ⇒ Object
- #collect_highlights(diff, old_highlights, new_highlights) ⇒ Object
-
#create_one_to_many_refinements(old, new, old_highlights, new_highlights) ⇒ Object
After returning from this method, both @refined_old and @refined_new must have been set to reasonable values.
-
#create_refinements(old, new, old_highlights, new_highlights) ⇒ Object
After returning from this method, both @refined_old and @refined_new must have been set to reasonable values.
-
#initialize(old, new) ⇒ Refiner
constructor
A new instance of Refiner.
- #render_refinement(prefix, color, string, highlights, base_index = 0) ⇒ Object
- #should_highlight?(old, new) ⇒ Boolean
- #try_highlight(old, new) ⇒ Object
- #try_highlight_initial_lines(old, new) ⇒ Object
Methods included from Colors
Constructor Details
#initialize(old, new) ⇒ Refiner
Returns a new instance of Refiner.
148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/refiner.rb', line 148 def initialize(old, new) old_highlights, new_highlights = try_highlight(old, new) if old_highlights.size == 0 && new_highlights.size == 0 old_highlights, new_highlights = try_highlight_initial_lines(old, new) end if !create_one_to_many_refinements(old, new, old_highlights, new_highlights) create_refinements(old, new, old_highlights, new_highlights) end end |
Instance Attribute Details
#refined_new ⇒ Object (readonly)
Returns the value of attribute refined_new.
20 21 22 |
# File 'lib/refiner.rb', line 20 def refined_new @refined_new end |
#refined_old ⇒ Object (readonly)
Returns the value of attribute refined_old.
19 20 21 |
# File 'lib/refiner.rb', line 19 def refined_old @refined_old end |
Instance Method Details
#censor_highlights(old, new, old_highlights, new_highlights) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/refiner.rb', line 42 def censor_highlights(old, new, old_highlights, new_highlights) old_highlights_percentage = 100 * old_highlights.size / old.length new_highlights_percentage = 100 * new_highlights.size / new.length if old_highlights_percentage > REFINEMENT_THRESHOLD \ || new_highlights_percentage > REFINEMENT_THRESHOLD # We'll consider this a replacement rather than a change, don't # highlight it. old_highlights.clear new_highlights.clear end end |
#collect_highlights(diff, old_highlights, new_highlights) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/refiner.rb', line 27 def collect_highlights(diff, old_highlights, new_highlights) diff.each do |section| section.each do |highlight| case highlight.action when '-' old_highlights << highlight.position when '+' new_highlights << highlight.position else fail("Unsupported diff type: <#{type}>") end end end end |
#create_one_to_many_refinements(old, new, old_highlights, new_highlights) ⇒ Object
After returning from this method, both @refined_old and @refined_new must have been set to reasonable values.
Returns false if the preconditions for using this method aren’t fulfilled
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/refiner.rb', line 117 def create_one_to_many_refinements(old, new, old_highlights, new_highlights) # If things have been removed from the first line, the specialized # highlighting won't work return false if old_highlights.count > 0 # If the first line was replaced rather than updated, the specialized # highlighting won't work return false if new_highlights.count == 0 # Specialized highlighting requires exactly one old line return false if old.lines.count != 1 lines = new.lines # Specialized highlighting requires two or more new lines return false if lines.count < 2 @refined_old = '' refined_line_1 = render_refinement(' ', '', lines[0], new_highlights) line_2_index_0 = lines[0].length refined_remaining_lines = render_refinement('+', GREEN, lines[1..-1].join, new_highlights, line_2_index_0) @refined_new = refined_line_1 + refined_remaining_lines return true end |
#create_refinements(old, new, old_highlights, new_highlights) ⇒ Object
After returning from this method, both @refined_old and @refined_new must have been set to reasonable values.
108 109 110 111 |
# File 'lib/refiner.rb', line 108 def create_refinements(old, new, old_highlights, new_highlights) @refined_old = render_refinement('-', RED, old, old_highlights) @refined_new = render_refinement('+', GREEN, new, new_highlights) end |
#render_refinement(prefix, color, string, highlights, base_index = 0) ⇒ Object
98 99 100 101 102 103 104 |
# File 'lib/refiner.rb', line 98 def render_refinement(prefix, color, string, highlights, base_index = 0) return_me = DiffString.new(prefix, color) string.each_char.with_index do |char, index| return_me.add(char, highlights.include?(index + base_index)) end return return_me.to_s end |
#should_highlight?(old, new) ⇒ Boolean
55 56 57 58 59 60 61 62 63 |
# File 'lib/refiner.rb', line 55 def should_highlight?(old, new) return false if old.empty? || new.empty? # The 15_000 constant has been determined using the "benchmark" # program in our bin/ directory. return false if old.length + new.length > 15_000 return true end |
#try_highlight(old, new) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/refiner.rb', line 65 def try_highlight(old, new) old_highlights = Set.new new_highlights = Set.new if should_highlight?(old, new) collect_highlights(Diff::LCS.diff(old, new), old_highlights, new_highlights) censor_highlights(old, new, old_highlights, new_highlights) end return old_highlights, new_highlights end |
#try_highlight_initial_lines(old, new) ⇒ Object
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/refiner.rb', line 79 def try_highlight_initial_lines(old, new) old_line_count = old.lines.count new_line_count = new.lines.count if old_line_count == new_line_count return Set.new, Set.new end min_line_count = [old_line_count, new_line_count].min if min_line_count == 0 return Set.new, Set.new end # Truncate old and new so they have the same number of lines old = old.lines[0..(min_line_count - 1)].join new = new.lines[0..(min_line_count - 1)].join return try_highlight(old, new) end |