patience_diff 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/History.txt +5 -0
- data/Manifest.txt +10 -2
- data/Rakefile +0 -1
- data/bin/patience_diff +29 -17
- data/lib/patience_diff.rb +7 -3
- data/lib/patience_diff/differ.rb +63 -0
- data/lib/patience_diff/formatter.rb +66 -0
- data/lib/patience_diff/formatting_context.rb +36 -0
- data/lib/patience_diff/html/escaping.rb +12 -0
- data/lib/patience_diff/html/formatter.rb +71 -0
- data/lib/patience_diff/html/header_helper.rb +16 -0
- data/lib/patience_diff/html/hunk_helper.rb +50 -0
- data/lib/patience_diff/sequence_matcher.rb +6 -3
- data/templates/html.erb +307 -0
- data/templates/html_header.erb +4 -0
- data/templates/html_hunk.erb +30 -0
- metadata +37 -58
- data/lib/patience_diff/card.rb +0 -9
- data/lib/patience_diff/unified_differ.rb +0 -66
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d37dfbd16df805a3d8b286765fe5ea12c665c156
|
4
|
+
data.tar.gz: f3df94dd269cba9149197be6ea584fa3c8f3f103
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c82eb37da25cbecb994d98fba1de7e355887a4576c7a50afa688d0e62f76c4887cc6d9f5a014d303fbfc92a1e17e6e999f7989ea17fcba6a06810885d786a212
|
7
|
+
data.tar.gz: 3c1fc29891874a7de6fcd510c466ec9c6088bff9ce6f8695f5c44b2a8035b52e708344c266b3f36b0b25cfc56d0534d1b2286da1109f49502e61b745d5c9b2ff
|
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -4,7 +4,15 @@ README.md
|
|
4
4
|
Rakefile
|
5
5
|
bin/patience_diff
|
6
6
|
lib/patience_diff.rb
|
7
|
-
lib/patience_diff/
|
7
|
+
lib/patience_diff/differ.rb
|
8
|
+
lib/patience_diff/formatter.rb
|
9
|
+
lib/patience_diff/formatting_context.rb
|
8
10
|
lib/patience_diff/sequence_matcher.rb
|
9
|
-
lib/patience_diff/unified_differ.rb
|
10
11
|
lib/patience_diff/usage_error.rb
|
12
|
+
lib/patience_diff/html/escaping.rb
|
13
|
+
lib/patience_diff/html/formatter.rb
|
14
|
+
lib/patience_diff/html/header_helper.rb
|
15
|
+
lib/patience_diff/html/hunk_helper.rb
|
16
|
+
templates/html.erb
|
17
|
+
templates/html_header.erb
|
18
|
+
templates/html_hunk.erb
|
data/Rakefile
CHANGED
data/bin/patience_diff
CHANGED
@@ -1,38 +1,50 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'English'
|
3
|
+
require 'pathname'
|
3
4
|
require 'trollop'
|
4
5
|
|
5
|
-
lib_path =
|
6
|
+
lib_path = Pathname(File.join(File.dirname(__FILE__),"..","lib")).realpath
|
6
7
|
$LOAD_PATH.unshift(lib_path)
|
7
8
|
require 'patience_diff'
|
8
9
|
|
9
10
|
begin
|
11
|
+
program_name = File.basename($PROGRAM_NAME)
|
10
12
|
opts = Trollop::options do
|
11
|
-
banner <<-EOF
|
12
|
-
Usage: #{
|
13
|
-
Options:
|
14
|
-
EOF
|
15
|
-
|
13
|
+
banner <<-EOF.gsub(/^\s*/, '')
|
14
|
+
Usage: #{program_name} [options] left-file right-file (left-file-2 right-file-2 ...)
|
15
|
+
Options:
|
16
|
+
EOF
|
17
|
+
|
18
|
+
version "#{program_name} #{PatienceDiff::VERSION}"
|
19
|
+
|
16
20
|
opt :debug, "Debugging mode"
|
17
21
|
opt :context, "Lines of context", :default => 3
|
18
|
-
opt :
|
22
|
+
opt :all_context, "Don't collapse common sections; output entire files. Ignored in html format."
|
23
|
+
opt :format, "Specify output format. Currently 'text' and 'html' are supported.", :default => "text"
|
19
24
|
opt :ignore_whitespace,
|
20
25
|
"Ignore trailing whitespace, and treat leading whitespace as either present or not. This switch is for compatibility with diff's -b option.",
|
21
26
|
:short => '-b'
|
22
27
|
end
|
23
28
|
|
24
|
-
raise PatienceDiff::UsageError unless ARGV.length ==
|
29
|
+
raise PatienceDiff::UsageError unless ARGV.length and ARGV.length % 2 == 0
|
25
30
|
|
26
|
-
|
31
|
+
differ = PatienceDiff::Differ.new(opts)
|
32
|
+
formatter = case opts.delete(:format)
|
33
|
+
when 'text'
|
34
|
+
PatienceDiff::Formatter
|
35
|
+
when 'html'
|
36
|
+
PatienceDiff::Html::Formatter
|
37
|
+
else
|
38
|
+
raise PatienceDiff::UsageError
|
39
|
+
end.new(differ)
|
27
40
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
puts formatter.diff(left, right, left_file, right_file, left_timestamp, right_timestamp)
|
41
|
+
formatter.format do |diff|
|
42
|
+
ARGV.each_slice(2) do |(left_file, right_file)|
|
43
|
+
diff.files left_file, right_file
|
44
|
+
end
|
45
|
+
end.tap do |result|
|
46
|
+
print result
|
47
|
+
end
|
36
48
|
|
37
49
|
rescue PatienceDiff::UsageError => e
|
38
50
|
Trollop.module_eval do
|
data/lib/patience_diff.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'pathname'
|
2
|
+
require 'patience_diff/differ'
|
3
|
+
require 'patience_diff/formatter'
|
4
|
+
require 'patience_diff/formatting_context'
|
2
5
|
require 'patience_diff/sequence_matcher'
|
3
|
-
require 'patience_diff/unified_differ'
|
4
6
|
require 'patience_diff/usage_error'
|
7
|
+
PatienceDiff.autoload(:Html, 'patience_diff/html/formatter')
|
5
8
|
|
6
9
|
module PatienceDiff
|
7
|
-
VERSION = "1.0
|
10
|
+
VERSION = "1.1.0"
|
11
|
+
TEMPLATE_PATH = Pathname(File.join(File.dirname(__FILE__),'..','templates')).realpath
|
8
12
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'patience_diff/formatter'
|
3
|
+
require 'patience_diff/sequence_matcher'
|
4
|
+
|
5
|
+
module PatienceDiff
|
6
|
+
class Differ
|
7
|
+
attr_reader :matcher
|
8
|
+
attr_accessor :all_context, :line_ending, :ignore_whitespace
|
9
|
+
|
10
|
+
# Options:
|
11
|
+
# * :all_context: Output the entirety of each file. This overrides the sequence matcher's context setting.
|
12
|
+
# * :line_ending: Delimiter to use when joining diff output. Defaults to $RS.
|
13
|
+
# * :ignore_whitespace: Before comparing lines, strip trailing whitespace, and treat leading whitespace
|
14
|
+
# as either present or not. Does not affect output.
|
15
|
+
# Any additional options (e.g. :context) are passed on to the sequence matcher.
|
16
|
+
def initialize(opts = {})
|
17
|
+
@all_context = opts.delete(:all_context)
|
18
|
+
@line_ending = opts.delete(:line_ending) || $RS
|
19
|
+
@ignore_whitespace = opts.delete(:ignore_whitespace)
|
20
|
+
@matcher = SequenceMatcher.new(opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generates a unified diff from the contents of the files at the paths specified.
|
24
|
+
def diff_files(left_file, right_file, formatter=Formatter.new)
|
25
|
+
(left_data, left_timestamp), (right_data, right_timestamp) = [left_file, right_file].map do |filename|
|
26
|
+
# Read in binary encoding, so that we can diff any encoding and split() won't complain
|
27
|
+
File.open(filename, :external_encoding => Encoding::BINARY) do |file|
|
28
|
+
[file.read.split($RS), file.mtime]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
diff_sequences(left_data, right_data, left_file, right_file, left_timestamp, right_timestamp, formatter)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Generate a unified diff of the data specified. The left and right values should be strings, or any other indexable, sortable data.
|
35
|
+
# File names and timestamps do not affect the diff algorithm, but are used in the header text.
|
36
|
+
def diff_sequences(left, right, left_name=nil, right_name=nil, left_timestamp=nil, right_timestamp=nil, formatter=Formatter.new)
|
37
|
+
if @ignore_whitespace
|
38
|
+
a = left.map { |line| line.rstrip.gsub(/^\s+/, ' ') }
|
39
|
+
b = right.map { |line| line.rstrip.gsub(/^\s+/, ' ') }
|
40
|
+
else
|
41
|
+
a = left
|
42
|
+
b = right
|
43
|
+
end
|
44
|
+
|
45
|
+
if @all_context
|
46
|
+
hunks = [@matcher.diff_opcodes(a, b)]
|
47
|
+
else
|
48
|
+
hunks = @matcher.grouped_opcodes(a, b)
|
49
|
+
end
|
50
|
+
|
51
|
+
return nil unless hunks.any?
|
52
|
+
|
53
|
+
lines = []
|
54
|
+
lines << formatter.render_header(left_name, right_name, left_timestamp, right_timestamp)
|
55
|
+
last_hunk_end = -1
|
56
|
+
hunks.each do |opcodes|
|
57
|
+
lines << formatter.render_hunk(a, b, opcodes, last_hunk_end)
|
58
|
+
last_hunk_end = opcodes.last[4]
|
59
|
+
end
|
60
|
+
lines.flatten.compact.join(@line_ending) + @line_ending
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'patience_diff/formatting_context'
|
2
|
+
|
3
|
+
module PatienceDiff
|
4
|
+
# Formats a plaintext unified diff.
|
5
|
+
class Formatter
|
6
|
+
attr_reader :names
|
7
|
+
attr_accessor :left_name, :right_name, :left_timestamp, :right_timestamp, :title
|
8
|
+
|
9
|
+
def initialize(differ, title = nil)
|
10
|
+
@differ = differ
|
11
|
+
@names = []
|
12
|
+
@title = title || "Diff generated on #{Time.now.strftime('%c')}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def format
|
16
|
+
context = FormattingContext.new(@differ, self)
|
17
|
+
yield context
|
18
|
+
context.format
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_header(left_name=nil, right_name=nil, left_timestamp=nil, right_timestamp=nil)
|
22
|
+
@names << right_name
|
23
|
+
@left_name = left_name || "Original"
|
24
|
+
@right_name = right_name || "Current"
|
25
|
+
@left_timestamp = left_timestamp || Time.now
|
26
|
+
@right_timestamp = right_timestamp || Time.now
|
27
|
+
[
|
28
|
+
left_header_line(@left_name, @left_timestamp),
|
29
|
+
right_header_line(@right_name, @right_timestamp)
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def render_hunk_marker(opcodes)
|
34
|
+
a_start = opcodes.first[1] + 1
|
35
|
+
a_end = opcodes.last[2] + 2
|
36
|
+
b_start = opcodes.first[3] + 1
|
37
|
+
b_end = opcodes.last[4] + 2
|
38
|
+
|
39
|
+
"@@ -%d,%d +%d,%d @@" % [a_start, a_end-a_start, b_start, b_end-b_start]
|
40
|
+
end
|
41
|
+
|
42
|
+
def render_hunk(a, b, opcodes, last_line_shown)
|
43
|
+
lines = [render_hunk_marker(opcodes)]
|
44
|
+
lines << opcodes.collect do |(code, a_start, a_end, b_start, b_end)|
|
45
|
+
case code
|
46
|
+
when :equal
|
47
|
+
b[b_start..b_end].map { |line| ' ' + line }
|
48
|
+
when :delete
|
49
|
+
a[a_start..a_end].map { |line| '-' + line }
|
50
|
+
when :insert
|
51
|
+
b[b_start..b_end].map { |line| '+' + line }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
lines
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def left_header_line(name, timestamp)
|
59
|
+
"--- %s\t%s" % [name, timestamp.strftime("%Y-%m-%d %H:%m:%S.%N %z")]
|
60
|
+
end
|
61
|
+
|
62
|
+
def right_header_line(name, timestamp)
|
63
|
+
"+++ %s\t%s" % [name, timestamp.strftime("%Y-%m-%d %H:%m:%S.%N %z")]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module PatienceDiff
|
4
|
+
# Delegate object yielded by the #format method.
|
5
|
+
class FormattingContext
|
6
|
+
def initialize(differ, formatter)
|
7
|
+
@differ = differ
|
8
|
+
@formatter = formatter
|
9
|
+
@out = StringIO.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def files(left_file, right_file)
|
13
|
+
@out.print @differ.diff_files(left_file, right_file, @formatter)
|
14
|
+
end
|
15
|
+
|
16
|
+
def sequences(left, right, left_name=nil, right_name=nil, left_timestamp=nil, right_timestamp=nil)
|
17
|
+
@out.print @differ.diff_sequences(left, right, left_name, right_name, left_timestamp, right_timestamp, @formatter)
|
18
|
+
end
|
19
|
+
|
20
|
+
def orphan(sequence, name=nil, timestamp=nil)
|
21
|
+
@out.print @formatter.render_orphan(sequence, name, timestamp)
|
22
|
+
end
|
23
|
+
|
24
|
+
def format
|
25
|
+
@out.string
|
26
|
+
end
|
27
|
+
|
28
|
+
def title
|
29
|
+
@formatter.title
|
30
|
+
end
|
31
|
+
|
32
|
+
def names
|
33
|
+
@formatter.names
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'patience_diff/formatter'
|
2
|
+
require 'patience_diff/formatting_context'
|
3
|
+
require 'patience_diff/html/header_helper'
|
4
|
+
require 'patience_diff/html/hunk_helper'
|
5
|
+
|
6
|
+
require 'erubis'
|
7
|
+
|
8
|
+
module PatienceDiff
|
9
|
+
module Html
|
10
|
+
|
11
|
+
# Produces a fancy HTML-formatted unified diff. All your friends will be jealous.
|
12
|
+
class Formatter < PatienceDiff::Formatter
|
13
|
+
def initialize(*args)
|
14
|
+
super(*args)
|
15
|
+
@erbs = Hash.new do |hash, key|
|
16
|
+
hash[key] = Erubis::Eruby.new(File.read(key))
|
17
|
+
end
|
18
|
+
@hunk_count = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def format
|
22
|
+
context = FormattingContext.new(@differ, self)
|
23
|
+
yield context
|
24
|
+
template.evaluate(context)
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_header(*args)
|
28
|
+
left_header, right_header = *super(*args)
|
29
|
+
helper = HeaderHelper.new(left_header, right_header, @names.count - 1)
|
30
|
+
template("html_header.erb").evaluate(helper)
|
31
|
+
end
|
32
|
+
|
33
|
+
def render_hunk(a, b, opcodes, last_hunk_end)
|
34
|
+
helper = hunk_context(a, b, render_hunk_marker(opcodes), opcodes, last_hunk_end)
|
35
|
+
template("html_hunk.erb").evaluate(helper)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Render a single file as if it were a diff with no changes & full context
|
39
|
+
def render_orphan(sequence, name, timestamp)
|
40
|
+
@names << name
|
41
|
+
left_header = "--- New file"
|
42
|
+
right_header = right_header_line(name, timestamp)
|
43
|
+
helper = HeaderHelper.new(left_header, right_header, @names.count - 1)
|
44
|
+
result = template("html_header.erb").evaluate(helper)
|
45
|
+
|
46
|
+
# create one opcode with the entire content
|
47
|
+
opcodes = [
|
48
|
+
[:equal, 0, sequence.length-1, 0, sequence.length-1]
|
49
|
+
]
|
50
|
+
helper = hunk_context(sequence, sequence, nil, opcodes, 0)
|
51
|
+
result << template("html_hunk.erb").evaluate(helper)
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def template(filename = "html.erb")
|
57
|
+
@erbs[template_path(filename)]
|
58
|
+
end
|
59
|
+
|
60
|
+
# override to add/change templates
|
61
|
+
def template_path(filename)
|
62
|
+
File.join(PatienceDiff::TEMPLATE_PATH, filename)
|
63
|
+
end
|
64
|
+
|
65
|
+
def hunk_context(a, b, hunk_marker, opcodes, last_hunk_end)
|
66
|
+
@hunk_count += 1
|
67
|
+
HunkHelper.new(a, b, render_hunk_marker(opcodes), opcodes, last_hunk_end, @hunk_count)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'patience_diff/html/escaping'
|
2
|
+
|
3
|
+
module PatienceDiff
|
4
|
+
module Html
|
5
|
+
class HeaderHelper
|
6
|
+
include Escaping
|
7
|
+
attr_accessor :left_header, :right_header, :header_id
|
8
|
+
|
9
|
+
def initialize(left_header, right_header, header_id)
|
10
|
+
@left_header = left_header
|
11
|
+
@right_header = right_header
|
12
|
+
@header_id = header_id
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'patience_diff/html/escaping'
|
2
|
+
|
3
|
+
module PatienceDiff
|
4
|
+
module Html
|
5
|
+
class HunkHelper
|
6
|
+
include Escaping
|
7
|
+
attr_accessor :a, :b, :hunk_marker, :opcodes, :last_hunk_end, :hunk_id
|
8
|
+
|
9
|
+
def initialize(a, b, hunk_marker, opcodes, last_hunk_end, hunk_id)
|
10
|
+
@a = a
|
11
|
+
@b = b
|
12
|
+
@hunk_marker = hunk_marker
|
13
|
+
@opcodes = opcodes
|
14
|
+
@last_hunk_end = last_hunk_end
|
15
|
+
@hunk_id = hunk_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def hunk_start
|
19
|
+
@opcodes.first[3]
|
20
|
+
end
|
21
|
+
|
22
|
+
def hidden_line_count
|
23
|
+
hunk_start - @last_hunk_end - 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def lines
|
27
|
+
@b
|
28
|
+
end
|
29
|
+
|
30
|
+
def each_line
|
31
|
+
opcodes.each do |(code, a_start, a_end, b_start, b_end)|
|
32
|
+
case code
|
33
|
+
when :delete
|
34
|
+
a[a_start..a_end].each { |line| yield 'delete', '-' + format_line(line) }
|
35
|
+
when :equal
|
36
|
+
b[b_start..b_end].each { |line| yield 'equal', ' ' + format_line(line) }
|
37
|
+
when :insert
|
38
|
+
b[b_start..b_end].each { |line| yield 'insert', '+' + format_line(line) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
# override for additional behavior, e.g. syntax highlighting
|
45
|
+
def format_line(line)
|
46
|
+
escape(line)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,9 +1,12 @@
|
|
1
|
-
require 'patience_diff/card'
|
2
|
-
|
3
1
|
module PatienceDiff
|
2
|
+
# Matches indexed data (generally text) using the Patience diff algorithm.
|
4
3
|
class SequenceMatcher
|
5
4
|
attr_accessor :context
|
6
5
|
|
6
|
+
Card = Struct.new(:index, :value, :previous)
|
7
|
+
|
8
|
+
# Options:
|
9
|
+
# * :context: number of lines of context to use when grouping
|
7
10
|
def initialize(opts = {})
|
8
11
|
@context = opts[:context] || 3
|
9
12
|
end
|
@@ -146,7 +149,7 @@ module PatienceDiff
|
|
146
149
|
b_mid -= 1
|
147
150
|
end
|
148
151
|
recursively_match(a, b, a_lo, b_lo, a_mid, b_mid) { |match| yield match }
|
149
|
-
0...(a_hi-a_mid).each do |i|
|
152
|
+
(0...(a_hi-a_mid)).each do |i|
|
150
153
|
yield [a_mid+i, b_mid+i]
|
151
154
|
end
|
152
155
|
end
|
data/templates/html.erb
ADDED
@@ -0,0 +1,307 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8" />
|
5
|
+
<title><%= title %></title>
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
font-family: Consolas, Menlo, Monaco, monospace;
|
9
|
+
font-size: 8pt;
|
10
|
+
}
|
11
|
+
.diff, .marker { white-space: pre; }
|
12
|
+
.diff:hover { background-color: #ff9; }
|
13
|
+
.delete { background-color: #fdd; }
|
14
|
+
.insert { background-color: #dfd; }
|
15
|
+
.divider {
|
16
|
+
position: relative;
|
17
|
+
background-color: #cce5ff; /* hsl(210, 100%, 90%) */
|
18
|
+
background-image: -webkit-linear-gradient(top, hsl(210, 100%, 95%) 0%, hsl(210, 100%, 90%) 100%);
|
19
|
+
background-image: -moz-linear-gradient(top, hsl(210, 100%, 95%) 0%, hsl(210, 100%, 90%) 100%);
|
20
|
+
background-image: -o-linear-gradient(top, hsl(210, 100%, 95%) 0%, hsl(210, 100%, 90%) 100%);
|
21
|
+
background-image: linear-gradient(top, hsl(210, 100%, 95%) 0%, hsl(210, 100%, 90%) 100%);
|
22
|
+
font-family: Calibri, "Lucida Grande", sans-serif;
|
23
|
+
border: solid 1px #b3cce6; /* hsl(210, 50%, 80%) */
|
24
|
+
padding: 0 2px;
|
25
|
+
border-radius: 3px;
|
26
|
+
cursor: default;
|
27
|
+
}
|
28
|
+
.toggle:hover {
|
29
|
+
background-image: -webkit-linear-gradient(top, hsl(210, 100%, 90%) 0%, hsl(210, 100%, 80%) 100%);
|
30
|
+
background-image: -moz-linear-gradient(top, hsl(210, 100%, 90%) 0%, hsl(210, 100%, 80%) 100%);
|
31
|
+
background-image: -o-linear-gradient(top, hsl(210, 100%, 90%) 0%, hsl(210, 100%, 80%) 100%);
|
32
|
+
background-image: linear-gradient(top, hsl(210, 100%, 90%) 0%, hsl(210, 100%, 80%) 100%);
|
33
|
+
border: solid 1px #39f; /* hsl(210, 100%, 60%) */
|
34
|
+
}
|
35
|
+
.marker {
|
36
|
+
color: #000080;
|
37
|
+
font-family: Consolas, Menlo, Monaco, monospace;
|
38
|
+
position: absolute;
|
39
|
+
}
|
40
|
+
.hint {
|
41
|
+
color: #999;
|
42
|
+
text-align: center;
|
43
|
+
}
|
44
|
+
.divider:hover .hint { color: black; }
|
45
|
+
.down_arrow, .up_arrow { position: relative; }
|
46
|
+
.down_arrow:after, .down_arrow:before { top: 100%; }
|
47
|
+
.up_arrow:after, .up_arrow:before { bottom: 100%; }
|
48
|
+
.down_arrow:after, .down_arrow:before, .up_arrow:after, .up_arrow:before {
|
49
|
+
border: solid transparent;
|
50
|
+
content: " ";
|
51
|
+
height: 0;
|
52
|
+
width: 0;
|
53
|
+
position: absolute;
|
54
|
+
left: 50%;
|
55
|
+
}
|
56
|
+
.down_arrow:after, .up_arrow:after { border-width: 5px; margin-left: -5px; }
|
57
|
+
.down_arrow:before, .up_arrow:before { border-width: 6px; margin-left: -6px; }
|
58
|
+
.down_arrow:after { border-top-color: hsl(210, 100%, 90%); }
|
59
|
+
.up_arrow:after { border-bottom-color: hsl(210, 100%, 95%); }
|
60
|
+
.down_arrow:before { border-top-color: hsl(210, 50%, 80%); }
|
61
|
+
.up_arrow:before { border-bottom-color: hsl(210, 50%, 80%); }
|
62
|
+
.toggle:hover .down_arrow:after { border-top-color: hsl(210, 100%, 80%); }
|
63
|
+
.toggle:hover .down_arrow:before { border-top-color: hsl(210, 100%, 60%); }
|
64
|
+
.toggle:hover .up_arrow:after { border-bottom-color: hsl(210, 100%, 90%); }
|
65
|
+
.toggle:hover .up_arrow:before { border-bottom-color: hsl(210, 100%, 60%); }
|
66
|
+
.left_header { page-break-before: always; }
|
67
|
+
.left_header, .right_header { background-color: #ddd; color: #222; }
|
68
|
+
#content .collapsed {
|
69
|
+
-webkit-transition: bottom .5s, height .5s;
|
70
|
+
-moz-transition: bottom .5s, height .5s;
|
71
|
+
-o-transition: bottom .5s, height .5s;
|
72
|
+
transition: bottom .5s, height .5s;
|
73
|
+
height: 0;
|
74
|
+
overflow: hidden
|
75
|
+
}
|
76
|
+
#nav_container {
|
77
|
+
position: fixed;
|
78
|
+
top: 0;
|
79
|
+
right: 0;
|
80
|
+
border-radius: 4px 0 0 4px / 50% 0 0 50%;
|
81
|
+
box-shadow: 0 0 8px 0 rgba(0,0,0,.5);
|
82
|
+
-webkit-transition: opacity 1s, bottom .5s, height .5s;
|
83
|
+
-moz-transition: opacity 1s, bottom .5s, height .5s;
|
84
|
+
-o-transition: opacity 1s, bottom .5s, height .5s;
|
85
|
+
transition: opacity 1s, bottom .5s, height .5s;
|
86
|
+
}
|
87
|
+
#nav {
|
88
|
+
position: absolute;
|
89
|
+
top: 0;
|
90
|
+
bottom: 0;
|
91
|
+
left: 0;
|
92
|
+
right: 0;
|
93
|
+
border-left: solid 1px #999;
|
94
|
+
overflow: hidden;
|
95
|
+
background: #eee;
|
96
|
+
background-image: -webkit-linear-gradient(left, #e7e7e7 0%, #eee 25%, #eee 75%, #e7e7e7 100%);
|
97
|
+
background-image: -moz-linear-gradient(left, #e7e7e7 0%, #eee 25%, #eee 75%, #e7e7e7 100%);
|
98
|
+
background-image: -o-linear-gradient(left, #e7e7e7 0%, #eee 25%, #eee 75%, #e7e7e7 100%);
|
99
|
+
background-image: linear-gradient(left, #e7e7e7 0%, #eee 25%, #eee 75%, #e7e7e7 100%);
|
100
|
+
font-family: Calibri, "Lucida Grande", sans-serif;
|
101
|
+
font-size: 10pt;
|
102
|
+
}
|
103
|
+
#nav_scroll {
|
104
|
+
position: absolute;
|
105
|
+
top: 1.75em;
|
106
|
+
bottom: 0;
|
107
|
+
right: 0;
|
108
|
+
overflow-x: hidden;
|
109
|
+
overflow-y: auto;
|
110
|
+
}
|
111
|
+
#nav button {
|
112
|
+
height: 1.75em;
|
113
|
+
margin: 0;
|
114
|
+
width: 100%;
|
115
|
+
background: #ddd;
|
116
|
+
background-image: -webkit-linear-gradient(top, #eee 0%, #ccc 100%);
|
117
|
+
background-image: -moz-linear-gradient(top, #eee 0%, #ccc 100%);
|
118
|
+
background-image: -o-linear-gradient(top, #eee 0%, #ccc 100%);
|
119
|
+
background-image: linear-gradient(top, #eee 0%, #ccc 100%);
|
120
|
+
border: none;
|
121
|
+
border-top: solid white 1px;
|
122
|
+
border-bottom: solid #999 1px;
|
123
|
+
font-family: Calibri, "Lucida Grande", sans-serif;
|
124
|
+
font-size: 10pt;
|
125
|
+
text-shadow: 0 1px 0 white;
|
126
|
+
}
|
127
|
+
#nav button:hover {
|
128
|
+
background: #eee;
|
129
|
+
background-image: -webkit-linear-gradient(top, #fff 0%, #ddd 100%);
|
130
|
+
background-image: -moz-linear-gradient(top, #fff 0%, #ddd 100%);
|
131
|
+
background-image: -o-linear-gradient(top, #fff 0%, #ddd 100%);
|
132
|
+
background-image: linear-gradient(top, #fff 0%, #ddd 100%);
|
133
|
+
}
|
134
|
+
#nav button:active {
|
135
|
+
border-top: solid black 1px;
|
136
|
+
background: #333;
|
137
|
+
background-image: -webkit-linear-gradient(top, #333 0%, #666 100%);
|
138
|
+
background-image: -moz-linear-gradient(top, #333 0%, #666 100%);
|
139
|
+
background-image: -o-linear-gradient(top, #333 0%, #666 100%);
|
140
|
+
background-image: linear-gradient(top, #333 0%, #666 100%);
|
141
|
+
color: white;
|
142
|
+
text-shadow: 0 1px 0 black;
|
143
|
+
}
|
144
|
+
#nav_container.collapsed { opacity: 0.33; }
|
145
|
+
#nav_container.collapsed:hover {
|
146
|
+
opacity: 1;
|
147
|
+
-webkit-transition: opacity 0s, bottom .5s, height .5s;
|
148
|
+
-moz-transition: opacity 0s, bottom .5s, height .5s;
|
149
|
+
-o-transition: opacity 0s, bottom .5s, height .5s;
|
150
|
+
transition: opacity 0s, bottom .5s, height .5s;
|
151
|
+
}
|
152
|
+
#nav ol { margin: 0.5em; padding: 0 15px 0 30px; }
|
153
|
+
#nav a { color: black; text-decoration: none; display: block; width: 100%; }
|
154
|
+
#nav a:hover { color: gray; }
|
155
|
+
#nav_container.no_animation {
|
156
|
+
-webkit-transition: none;
|
157
|
+
-moz-transition: none;
|
158
|
+
-o-transition: all 0 none;
|
159
|
+
transition: none;
|
160
|
+
}
|
161
|
+
</style>
|
162
|
+
<script>
|
163
|
+
function $(id) {
|
164
|
+
return document.getElementById(id);
|
165
|
+
}
|
166
|
+
function show(id) {
|
167
|
+
$(id).style.display = "";
|
168
|
+
}
|
169
|
+
function hide(id) {
|
170
|
+
$(id).style.display = "none";
|
171
|
+
}
|
172
|
+
function hasClass(el, classname) {
|
173
|
+
var classes = el.className.split(/\s+/);
|
174
|
+
for (var i = 0, len = classes.length; i < len; i++) {
|
175
|
+
if (classes[i] == classname)
|
176
|
+
return true;
|
177
|
+
}
|
178
|
+
return false;
|
179
|
+
}
|
180
|
+
function init() {
|
181
|
+
// Set nav container width to the natural width of its absolutely positioned content
|
182
|
+
var navWidth = $("nav_scroll").offsetWidth;
|
183
|
+
var container = $("nav_container");
|
184
|
+
container.style.width = navWidth + "px";
|
185
|
+
if (container.addEventListener) {
|
186
|
+
container.addEventListener("webkitTransitionEnd", page.navTransitioned);
|
187
|
+
container.addEventListener("transitionend", page.navTransitioned);
|
188
|
+
container.addEventListener("oTransitionEnd", page.navTransitioned);
|
189
|
+
}
|
190
|
+
$("nav_hide").onclick = page.hideNav;
|
191
|
+
$("nav_show").onclick = page.showNav;
|
192
|
+
|
193
|
+
// set up nav links
|
194
|
+
function createJump(id) {
|
195
|
+
return function() {
|
196
|
+
page.jump(id);
|
197
|
+
return false;
|
198
|
+
}
|
199
|
+
}
|
200
|
+
var jumps = $("nav_list").getElementsByTagName("a");
|
201
|
+
for (var i = 0, len = jumps.length; i < len; i++) {
|
202
|
+
jumps[i].onclick = createJump("header_" + i);
|
203
|
+
}
|
204
|
+
|
205
|
+
// set up toggling of collapsed lines
|
206
|
+
function createToggle(hunkStart) {
|
207
|
+
return function() {
|
208
|
+
page.toggleCollapse(hunkStart);
|
209
|
+
return false;
|
210
|
+
}
|
211
|
+
}
|
212
|
+
var divs = $("content").getElementsByTagName("div");
|
213
|
+
for (var i = 0, len = divs.length; i < len; i++) {
|
214
|
+
var div = divs[i];
|
215
|
+
if (hasClass(div, "toggle")) {
|
216
|
+
var hunkStart = parseInt(div.id.replace(/^[^\d]*/,""));
|
217
|
+
if (hunkStart) {
|
218
|
+
div.onclick = createToggle(hunkStart);
|
219
|
+
}
|
220
|
+
}
|
221
|
+
}
|
222
|
+
}
|
223
|
+
function windowHeight() {
|
224
|
+
return window.innerHeight || document.documentElement.clientHeight;
|
225
|
+
}
|
226
|
+
page = {
|
227
|
+
hideNav: function() {
|
228
|
+
hide("nav_hide");
|
229
|
+
show("nav_show");
|
230
|
+
var container = $("nav_container");
|
231
|
+
var buttonHeight = $("nav_buttons").offsetHeight + "px";
|
232
|
+
container.className = "no_animation";
|
233
|
+
// set a fixed height, and clear the bottom
|
234
|
+
container.style.height = windowHeight() + "px";
|
235
|
+
container.style.bottom = "auto";
|
236
|
+
// this is a hack to make sure the height is recalculated so that the transition animation triggers
|
237
|
+
setTimeout(function() {
|
238
|
+
container.className = "";
|
239
|
+
container.style.height = buttonHeight;
|
240
|
+
}, 10);
|
241
|
+
},
|
242
|
+
showNav: function() {
|
243
|
+
hide("nav_show");
|
244
|
+
show("nav_hide");
|
245
|
+
var container = $("nav_container");
|
246
|
+
container.className = "no_animation";
|
247
|
+
// set a fixed bottom, and clear the height
|
248
|
+
container.style.bottom = (windowHeight() - $("nav_buttons").offsetHeight) + "px";
|
249
|
+
container.style.height = "auto";
|
250
|
+
setTimeout(function() {
|
251
|
+
container.className = "";
|
252
|
+
container.style.bottom = "0";
|
253
|
+
}, 10);
|
254
|
+
},
|
255
|
+
navTransitioned: function() {
|
256
|
+
var container = $("nav_container");
|
257
|
+
if (!container.classList)
|
258
|
+
return;
|
259
|
+
if (container.style.bottom === "auto") {
|
260
|
+
container.classList.add("collapsed");
|
261
|
+
} else {
|
262
|
+
container.classList.remove("collapsed");
|
263
|
+
}
|
264
|
+
},
|
265
|
+
jump: function(id) {
|
266
|
+
scrollTo(0,$(id).offsetTop);
|
267
|
+
},
|
268
|
+
toggleCollapse: function(line) {
|
269
|
+
var growDiv = $("hunk_" + line);
|
270
|
+
if (growDiv.offsetHeight) {
|
271
|
+
growDiv.style.height = 0;
|
272
|
+
hide("collapse_" + line);
|
273
|
+
show("expand_" + line);
|
274
|
+
} else {
|
275
|
+
var wrapper = growDiv.getElementsByTagName("div")[0];
|
276
|
+
growDiv.style.height = wrapper.offsetHeight + "px";
|
277
|
+
hide("expand_" + line);
|
278
|
+
show("collapse_" + line);
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
</script>
|
283
|
+
<!--[if lte IE 7]>
|
284
|
+
<style>
|
285
|
+
.unicode { font-family: "Lucida Sans Unicode"; }
|
286
|
+
</style>
|
287
|
+
<![endif]-->
|
288
|
+
</head>
|
289
|
+
<body onload="init();">
|
290
|
+
<div id="content"><%= format %></div>
|
291
|
+
<% if names.count > 1 %>
|
292
|
+
<div id="nav_container" style="bottom: 0; height: 100%"><div id="nav">
|
293
|
+
<div id="nav_buttons">
|
294
|
+
<button id="nav_hide"><span class="unicode">▾</span> Navigation</button>
|
295
|
+
<button id="nav_show" style="display: none"><span class="unicode">▸</span> Navigation</button>
|
296
|
+
</div>
|
297
|
+
<div id="nav_scroll">
|
298
|
+
<ol id="nav_list">
|
299
|
+
<% names.each_with_index do |name, i| %>
|
300
|
+
<li><a href="#header_<%= i %>"><%= name %></a></li>
|
301
|
+
<% end %>
|
302
|
+
</ol>
|
303
|
+
</div>
|
304
|
+
</div></div>
|
305
|
+
<% end %>
|
306
|
+
</body>
|
307
|
+
</html>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<% if hidden_line_count > 0 %>
|
2
|
+
<div class="collapsed" id="hunk_<%= hunk_id %>">
|
3
|
+
<div>
|
4
|
+
<div class="divider toggle" id="toggle_collapse_<%= hunk_id %>">
|
5
|
+
<div class="hint down_arrow">Click to collapse <%= hidden_line_count %> lines</div>
|
6
|
+
</div>
|
7
|
+
<% lines[(last_hunk_end+1)...hunk_start].each do |line| %>
|
8
|
+
<div class="equal diff"> <%= format_line(line) %></div>
|
9
|
+
<% end %>
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
<div class="divider toggle" id="toggle_expand_<%= hunk_id %>">
|
13
|
+
<span class="marker"><%= escape(hunk_marker) %></span>
|
14
|
+
<div class="hint down_arrow" id="expand_<%= hunk_id %>">
|
15
|
+
Click to expand <%= hidden_line_count %> lines
|
16
|
+
</div>
|
17
|
+
<div class="hint up_arrow" id="collapse_<%= hunk_id %>" style="display: none">
|
18
|
+
Click to collapse <%= hidden_line_count %> lines
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
<% elsif hunk_marker.to_s == "" %>
|
22
|
+
<div class="divider">
|
23
|
+
<span class="marker"><%= escape(hunk_marker) %></span>
|
24
|
+
|
25
|
+
</div>
|
26
|
+
<% end %>
|
27
|
+
|
28
|
+
<% each_line do |code, formatted_line| %>
|
29
|
+
<div class="<%= code %> diff"><%= formatted_line %></div>
|
30
|
+
<% end %>
|
metadata
CHANGED
@@ -1,96 +1,66 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: patience_diff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Andrew Watt
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-10-23 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: trollop
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- - ~>
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '1.16'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- - ~>
|
24
|
+
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '1.16'
|
30
|
-
- !ruby/object:Gem::Dependency
|
31
|
-
name: rubyforge
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
|
-
requirements:
|
35
|
-
- - ! '>='
|
36
|
-
- !ruby/object:Gem::Version
|
37
|
-
version: 2.0.4
|
38
|
-
type: :development
|
39
|
-
prerelease: false
|
40
|
-
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
|
-
requirements:
|
43
|
-
- - ! '>='
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: 2.0.4
|
46
27
|
- !ruby/object:Gem::Dependency
|
47
28
|
name: rdoc
|
48
29
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
30
|
requirements:
|
51
|
-
- - ~>
|
31
|
+
- - "~>"
|
52
32
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
33
|
+
version: '4.0'
|
54
34
|
type: :development
|
55
35
|
prerelease: false
|
56
36
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
37
|
requirements:
|
59
|
-
- - ~>
|
38
|
+
- - "~>"
|
60
39
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
40
|
+
version: '4.0'
|
62
41
|
- !ruby/object:Gem::Dependency
|
63
42
|
name: hoe
|
64
43
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
44
|
requirements:
|
67
|
-
- - ~>
|
45
|
+
- - "~>"
|
68
46
|
- !ruby/object:Gem::Version
|
69
|
-
version: '3.
|
47
|
+
version: '3.13'
|
70
48
|
type: :development
|
71
49
|
prerelease: false
|
72
50
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
51
|
requirements:
|
75
|
-
- - ~>
|
52
|
+
- - "~>"
|
76
53
|
- !ruby/object:Gem::Version
|
77
|
-
version: '3.
|
78
|
-
description:
|
79
|
-
|
80
|
-
|
81
|
-
Patience Diff creates more readable diffs than other algorithms in some cases, particularly
|
82
|
-
when much of the content has changed between the documents being compared. There''s
|
83
|
-
a great explanation and example [here][example].
|
54
|
+
version: '3.13'
|
55
|
+
description: |-
|
56
|
+
A Ruby implementation of the Patience diff algorithm.
|
84
57
|
|
58
|
+
Patience Diff creates more readable diffs than other algorithms in some cases, particularly when much of the content has changed between the documents being compared. There's a great explanation and example [here][example].
|
85
59
|
|
86
|
-
Patience diff was originally written by Bram Cohen and is used in the [Bazaar][bazaar]
|
87
|
-
version control system. This version is loosely based off the Python implementation
|
88
|
-
in Bazaar.
|
89
|
-
|
60
|
+
Patience diff was originally written by Bram Cohen and is used in the [Bazaar][bazaar] version control system. This version is loosely based off the Python implementation in Bazaar.
|
90
61
|
|
91
62
|
[example]: http://alfedenzo.livejournal.com/170301.html
|
92
|
-
|
93
|
-
[bazaar]: http://bazaar.canonical.com/'
|
63
|
+
[bazaar]: http://bazaar.canonical.com/
|
94
64
|
email:
|
95
65
|
- andrew@wattornot.com
|
96
66
|
executables:
|
@@ -99,6 +69,7 @@ extensions: []
|
|
99
69
|
extra_rdoc_files:
|
100
70
|
- History.txt
|
101
71
|
- Manifest.txt
|
72
|
+
- README.md
|
102
73
|
files:
|
103
74
|
- History.txt
|
104
75
|
- Manifest.txt
|
@@ -106,34 +77,42 @@ files:
|
|
106
77
|
- Rakefile
|
107
78
|
- bin/patience_diff
|
108
79
|
- lib/patience_diff.rb
|
109
|
-
- lib/patience_diff/
|
80
|
+
- lib/patience_diff/differ.rb
|
81
|
+
- lib/patience_diff/formatter.rb
|
82
|
+
- lib/patience_diff/formatting_context.rb
|
83
|
+
- lib/patience_diff/html/escaping.rb
|
84
|
+
- lib/patience_diff/html/formatter.rb
|
85
|
+
- lib/patience_diff/html/header_helper.rb
|
86
|
+
- lib/patience_diff/html/hunk_helper.rb
|
110
87
|
- lib/patience_diff/sequence_matcher.rb
|
111
|
-
- lib/patience_diff/unified_differ.rb
|
112
88
|
- lib/patience_diff/usage_error.rb
|
89
|
+
- templates/html.erb
|
90
|
+
- templates/html_header.erb
|
91
|
+
- templates/html_hunk.erb
|
113
92
|
homepage: http://github.com/watt/ruby_patience_diff
|
114
|
-
licenses:
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
115
96
|
post_install_message:
|
116
97
|
rdoc_options:
|
117
|
-
- --main
|
98
|
+
- "--main"
|
118
99
|
- README.md
|
119
100
|
require_paths:
|
120
101
|
- lib
|
121
102
|
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
103
|
requirements:
|
124
|
-
- -
|
104
|
+
- - ">="
|
125
105
|
- !ruby/object:Gem::Version
|
126
106
|
version: '0'
|
127
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
-
none: false
|
129
108
|
requirements:
|
130
|
-
- -
|
109
|
+
- - ">="
|
131
110
|
- !ruby/object:Gem::Version
|
132
111
|
version: '0'
|
133
112
|
requirements: []
|
134
|
-
rubyforge_project:
|
135
|
-
rubygems_version:
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.2.2
|
136
115
|
signing_key:
|
137
|
-
specification_version:
|
116
|
+
specification_version: 4
|
138
117
|
summary: A Ruby implementation of the Patience diff algorithm
|
139
118
|
test_files: []
|
data/lib/patience_diff/card.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
require 'English'
|
2
|
-
|
3
|
-
module PatienceDiff
|
4
|
-
class UnifiedDiffer
|
5
|
-
attr_reader :matcher
|
6
|
-
attr_accessor :no_grouping, :line_ending, :ignore_whitespace
|
7
|
-
|
8
|
-
def initialize(opts = {})
|
9
|
-
@no_grouping = opts.delete(:no_grouping)
|
10
|
-
@line_ending = opts.delete(:line_ending) || $RS
|
11
|
-
@ignore_whitespace = opts.delete(:ignore_whitespace)
|
12
|
-
@matcher = SequenceMatcher.new(opts)
|
13
|
-
end
|
14
|
-
|
15
|
-
def diff(left, right, left_name=nil, right_name=nil, left_timestamp=nil, right_timestamp=nil)
|
16
|
-
left_name ||= "Original"
|
17
|
-
right_name ||= "Current"
|
18
|
-
left_timestamp ||= right_timestamp || Time.now
|
19
|
-
right_timestamp ||= left_timestamp || Time.now
|
20
|
-
|
21
|
-
if @ignore_whitespace
|
22
|
-
puts "ignoring whitespace"
|
23
|
-
a = left.map { |line| line.rstrip.gsub(/^\s+/, ' ') }
|
24
|
-
b = right.map { |line| line.rstrip.gsub(/^\s+/, ' ') }
|
25
|
-
else
|
26
|
-
a = left
|
27
|
-
b = right
|
28
|
-
end
|
29
|
-
|
30
|
-
if @no_grouping
|
31
|
-
groups = [@matcher.diff_opcodes(a, b)]
|
32
|
-
else
|
33
|
-
groups = @matcher.grouped_opcodes(a, b)
|
34
|
-
end
|
35
|
-
[
|
36
|
-
"--- %s\t%s" % [left_name, left_timestamp.strftime("%Y-%m-%d %H:%m:%S.%N %z")],
|
37
|
-
"+++ %s\t%s" % [right_name, right_timestamp.strftime("%Y-%m-%d %H:%m:%S.%N %z")],
|
38
|
-
groups.collect { |group| unified_diff_group(left, right, group) }.flatten.compact
|
39
|
-
].join(@line_ending)
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
def unified_diff_group(a, b, opcodes)
|
44
|
-
return nil if opcodes.empty?
|
45
|
-
|
46
|
-
a_start = opcodes.first[1] + 1
|
47
|
-
a_end = opcodes.last[2] + 2
|
48
|
-
b_start = opcodes.first[3] + 1
|
49
|
-
b_end = opcodes.last[4] + 2
|
50
|
-
|
51
|
-
lines = ["@@ -%d,%d +%d,%d @@" % [a_start, a_end-a_start, b_start, b_end-b_start]]
|
52
|
-
|
53
|
-
lines << opcodes.collect do |(code, a_start, a_end, b_start, b_end)|
|
54
|
-
case code
|
55
|
-
when :equal
|
56
|
-
b[b_start..b_end].map { |line| ' ' + line }
|
57
|
-
when :delete
|
58
|
-
a[a_start..a_end].map { |line| '-' + line }
|
59
|
-
when :insert
|
60
|
-
b[b_start..b_end].map { |line| '+' + line }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
lines
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|