This is admittedly ghetto, but I needed a super simple small-scale solution to creating unified diffs based off large blocks of text. The easiest way to do this was by creating temp file and using the native diff
command to do the comparison.
I’ve used this method for a really small snippets app for our intranet, which can be found on GitHub.
require 'tempfile'
# TODO: do some pre-comparison on a and b so we can skip creating temp files and all that jazz.
# NOTE: this is so ghetto ;)
module Diffable
def diff(b, options = {})
Diff.new(self, b, options).diff
end
end
class Diff
attr_reader :output, :changed
alias_method :changed?, :changed
alias_method :to_s, :output
def initialize(a, b, options = {})
@a = a.to_str
@b = b.to_str
@options = options
@changed = false
@output = ''
end
def diff
file_a = string_to_file('a', @a).path
file_b = string_to_file('b', @b).path
@output = format_output(`diff --unified=-1 #{file_a} #{file_b}`)
@changed = !@output.strip.empty?
self
end
def to_html(options = {})
diff.output.each_line.inject("") do |output, line|
output + \
case line[0]
when '@'
"<div class='#{options[:meta_class] || 'diff_meta'}'>#{line}</div>"
when '-'
"<div class='#{options[:sub_class] || 'diff_sub'}'>#{line}</div>"
when '+'
"<div class='#{options[:add_class] || 'diff_add'}'>#{line}</div>"
else
line
end
end
end
def to_s
output
end
private
def string_to_file(key, data)
Tempfile.open("#{key}.tmp_diff") do |file|
file << data.to_str
end
end
def format_output(output)
output.gsub! /\-{3}.+/, "--- #{@options[:mine]}" if @options[:mine]
output.gsub! /\+{3}.+/, "+++ #{@options[:theirs]}" if @options[:theirs]
output
end
end
class String
include Diffable
end
Usage
require 'diff'
require 'pp'
original = "
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Donec convallis urna nec magna ornare sit amet molestie leo dapibus.
Maecenas augue tortor, eleifend sed consectetur consectetur, aliquet et risus.
Suspendisse eu tellus ac quam adipiscing placerat.
In a erat quis dui euismod interdum.
In sapien lacus, suscipit sit amet faucibus sit amet, malesuada vehicula purus.
Etiam in odio ut urna viverra rhoncus.
Etiam ultricies tellus a metus adipiscing rutrum.
"
modified = "
Donec convallis urna nec magna ornare sit amet molestie leo dapibus.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Etiam in odio ut urna viverra rhoncus.
Etiam ultricies tellus a metus adipiscing rutrum.
Suspendisse eu tellus ac quam adipiscing placerat.
In sapien lacus, suscipit sit amet faucibus sit amet, malesuada vehicula purus.
In a erat quis dui euismod interdum.
Maecenas augue tortor, eleifend sed consectetur consectetur, aliquet et risus.
"
puts original.diff(modified, :mine => 'original', :theirs => 'modified')
__END__
--- original
+++ modified
@@ -1,9 +1,9 @@
-Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Donec convallis urna nec magna ornare sit amet molestie leo dapibus.
-Maecenas augue tortor, eleifend sed consectetur consectetur, aliquet et risus.
-Suspendisse eu tellus ac quam adipiscing placerat.
-In a erat quis dui euismod interdum.
-In sapien lacus, suscipit sit amet faucibus sit amet, malesuada vehicula purus.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Etiam in odio ut urna viverra rhoncus.
Etiam ultricies tellus a metus adipiscing rutrum.
+Suspendisse eu tellus ac quam adipiscing placerat.
+In sapien lacus, suscipit sit amet faucibus sit amet, malesuada vehicula purus.
+In a erat quis dui euismod interdum.
+Maecenas augue tortor, eleifend sed consectetur consectetur, aliquet et risus.
It can use some work (definitely use some work), but it gets the job done. I’ve also tried to fit this diff library into the build, but it wasn’t what I was looking for at the time.