Module: Yorishiro::Diff
- Defined in:
- lib/yorishiro/diff.rb
Overview
Minimal unified-diff generator used to preview file changes in the permission prompt. A single hunk is built by stripping the common prefix/suffix lines — accurate for the local edits tools make, and O(n) without an external diff gem.
Constant Summary collapse
- CONTEXT_LINES =
3- MAX_DIFF_LINES =
200
Class Method Summary collapse
-
.colorize(diff_text) ⇒ Object
Adds ANSI colors to a diff produced by .unified.
- .colorize_line(line) ⇒ Object
- .common_prefix_length(old_lines, new_lines) ⇒ Object
- .common_suffix_length(old_lines, new_lines, prefix) ⇒ Object
- .header(old_text, path) ⇒ Object
- .hunk(old_lines, prefix, suffix, removed, added) ⇒ Object
- .truncate(lines) ⇒ Object
-
.unified(old_text, new_text, path:) ⇒ Object
Returns a unified diff between the two texts, or nil when they are identical.
Class Method Details
.colorize(diff_text) ⇒ Object
Adds ANSI colors to a diff produced by .unified. Kept separate so callers can decide whether the output supports color.
34 35 36 |
# File 'lib/yorishiro/diff.rb', line 34 def colorize(diff_text) diff_text.lines(chomp: true).map { |line| colorize_line(line) }.join("\n") end |
.colorize_line(line) ⇒ Object
81 82 83 84 85 86 87 88 89 90 |
# File 'lib/yorishiro/diff.rb', line 81 def colorize_line(line) return line if line.start_with?("--- ", "+++ ") case line when /\A@@/ then "\e[36m#{line}\e[0m" when /\A\+/ then "\e[32m#{line}\e[0m" when /\A-/ then "\e[31m#{line}\e[0m" else line end end |
.common_prefix_length(old_lines, new_lines) ⇒ Object
38 39 40 41 42 |
# File 'lib/yorishiro/diff.rb', line 38 def common_prefix_length(old_lines, new_lines) max = [old_lines.length, new_lines.length].min (0...max).each { |i| return i if old_lines[i] != new_lines[i] } max end |
.common_suffix_length(old_lines, new_lines, prefix) ⇒ Object
44 45 46 47 48 |
# File 'lib/yorishiro/diff.rb', line 44 def common_suffix_length(old_lines, new_lines, prefix) max = [old_lines.length, new_lines.length].min - prefix (0...max).each { |i| return i if old_lines[-1 - i] != new_lines[-1 - i] } max end |
.header(old_text, path) ⇒ Object
50 51 52 53 54 55 |
# File 'lib/yorishiro/diff.rb', line 50 def header(old_text, path) # "/dev/null" is the unified-diff convention for a new file, not a # filesystem path, so File::NULL would be wrong on Windows. original = old_text.empty? ? "/dev/null" : path # rubocop:disable Style/FileNull "--- #{original}\n+++ #{path}\n" end |
.hunk(old_lines, prefix, suffix, removed, added) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/yorishiro/diff.rb', line 57 def hunk(old_lines, prefix, suffix, removed, added) context_start = [prefix - CONTEXT_LINES, 0].max before = old_lines[context_start...prefix] || [] after = old_lines[old_lines.length - suffix, CONTEXT_LINES] || [] old_count = before.length + removed.length + after.length new_count = before.length + added.length + after.length old_start = old_count.zero? ? 0 : context_start + 1 new_start = new_count.zero? ? 0 : context_start + 1 lines = ["@@ -#{old_start},#{old_count} +#{new_start},#{new_count} @@"] lines += before.map { |l| " #{l}" } lines += removed.map { |l| "-#{l}" } lines += added.map { |l| "+#{l}" } lines += after.map { |l| " #{l}" } truncate(lines).join("\n") end |
.truncate(lines) ⇒ Object
75 76 77 78 79 |
# File 'lib/yorishiro/diff.rb', line 75 def truncate(lines) return lines if lines.length <= MAX_DIFF_LINES lines.first(MAX_DIFF_LINES) + ["... (diff truncated, #{lines.length - MAX_DIFF_LINES} more lines)"] end |
.unified(old_text, new_text, path:) ⇒ Object
Returns a unified diff between the two texts, or nil when they are identical. When old_text is empty the file is treated as new (--- /dev/null, every line added).
17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/yorishiro/diff.rb', line 17 def unified(old_text, new_text, path:) return nil if old_text == new_text old_lines = old_text.lines(chomp: true) new_lines = new_text.lines(chomp: true) prefix = common_prefix_length(old_lines, new_lines) suffix = common_suffix_length(old_lines, new_lines, prefix) removed = old_lines[prefix...(old_lines.length - suffix)] || [] added = new_lines[prefix...(new_lines.length - suffix)] || [] header(old_text, path) + hunk(old_lines, prefix, suffix, removed, added) end |