Module: Shadwire::Differ
- Defined in:
- lib/shadwire/differ.rb
Overview
Minimal line-based unified diff, stdlib only — no diff gem. Used by diff
and update to show how a locally-edited file drifts from the registry copy.
Returns "" when the two texts are identical. Output normalizes line endings
(each rendered line is chomped) since it is for human display, not patching;
byte-exact drift detection is done by the caller comparing raw content.
Constant Summary collapse
- CONTEXT =
3
Class Method Summary collapse
-
.backtrack(table, a, b) ⇒ Object
Walks the table from the bottom-right to recover the edit sequence.
-
.group(edits) ⇒ Object
Groups changed edits into hunks, each padded with CONTEXT lines and merged when they overlap.
- .header(hunk) ⇒ Object
-
.lcs_table(a, b) ⇒ Object
Longest-common-subsequence length table over the two line arrays.
- .prefix(kind) ⇒ Object
- .render(hunks, path) ⇒ Object
- .unified(old_text, new_text, path: nil) ⇒ Object
Class Method Details
.backtrack(table, a, b) ⇒ Object
Walks the table from the bottom-right to recover the edit sequence.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/shadwire/differ.rb', line 39 def backtrack(table, a, b) edits = [] i = a.length j = b.length while i.positive? || j.positive? if i.positive? && j.positive? && a[i - 1] == b[j - 1] edits.unshift([:eq, i - 1, j - 1, a[i - 1]]) i -= 1 j -= 1 elsif j.positive? && (i.zero? || table[i][j - 1] >= table[i - 1][j]) edits.unshift([:ins, nil, j - 1, b[j - 1]]) j -= 1 else edits.unshift([:del, i - 1, nil, a[i - 1]]) i -= 1 end end edits end |
.group(edits) ⇒ Object
Groups changed edits into hunks, each padded with CONTEXT lines and merged when they overlap.
61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/shadwire/differ.rb', line 61 def group(edits) ranges = [] edits.each_index.select { |k| edits[k][0] != :eq }.each do |k| lo = [k - CONTEXT, 0].max hi = [k + CONTEXT, edits.length - 1].min if ranges.any? && lo <= ranges.last[1] + 1 ranges.last[1] = [ranges.last[1], hi].max else ranges << [lo, hi] end end ranges.map { |lo, hi| edits[lo..hi] } end |
.header(hunk) ⇒ Object
85 86 87 88 89 90 91 |
# File 'lib/shadwire/differ.rb', line 85 def header(hunk) old = hunk.reject { |kind, *| kind == :ins } new = hunk.reject { |kind, *| kind == :del } old_start = (old.first&.at(1) || 0) + 1 new_start = (new.first&.at(2) || 0) + 1 "@@ -#{old_start},#{old.size} +#{new_start},#{new.size} @@" end |
.lcs_table(a, b) ⇒ Object
Longest-common-subsequence length table over the two line arrays.
28 29 30 31 32 33 34 35 36 |
# File 'lib/shadwire/differ.rb', line 28 def lcs_table(a, b) table = Array.new(a.length + 1) { Array.new(b.length + 1, 0) } a.each_index do |i| b.each_index do |j| table[i + 1][j + 1] = a[i] == b[j] ? table[i][j] + 1 : [table[i][j + 1], table[i + 1][j]].max end end table end |
.prefix(kind) ⇒ Object
93 94 95 |
# File 'lib/shadwire/differ.rb', line 93 def prefix(kind) { eq: " ", del: "-", ins: "+" }.fetch(kind) end |
.render(hunks, path) ⇒ Object
75 76 77 78 79 80 81 82 83 |
# File 'lib/shadwire/differ.rb', line 75 def render(hunks, path) lines = [] lines << "--- a/#{path}" << "+++ b/#{path}" if path hunks.each do |hunk| lines << header(hunk) hunk.each { |kind, _, _, text| lines << "#{prefix(kind)}#{text.chomp}" } end "#{lines.join("\n")}\n" end |
.unified(old_text, new_text, path: nil) ⇒ Object
17 18 19 20 21 22 23 24 25 |
# File 'lib/shadwire/differ.rb', line 17 def unified(old_text, new_text, path: nil) return "" if old_text == new_text edits = backtrack(lcs_table(old_text.lines, new_text.lines), old_text.lines, new_text.lines) hunks = group(edits) return "" if hunks.empty? render(hunks, path) end |