patience_diff 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/Manifest.txt +10 -0
- data/README.md +61 -0
- data/Rakefile +10 -0
- data/bin/patience_diff +47 -0
- data/lib/patience_diff.rb +8 -0
- data/lib/patience_diff/card.rb +9 -0
- data/lib/patience_diff/sequence_matcher.rb +253 -0
- data/lib/patience_diff/unified_differ.rb +66 -0
- data/lib/patience_diff/usage_error.rb +3 -0
- metadata +139 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# ruby_patience_diff
|
2
|
+
|
3
|
+
* http://github.com/watt/ruby_patience_diff
|
4
|
+
|
5
|
+
## DESCRIPTION:
|
6
|
+
|
7
|
+
A Ruby implementation of the Patience diff algorithm.
|
8
|
+
|
9
|
+
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].
|
10
|
+
|
11
|
+
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.
|
12
|
+
|
13
|
+
[example]: http://alfedenzo.livejournal.com/170301.html
|
14
|
+
[bazaar]: http://bazaar.canonical.com/
|
15
|
+
|
16
|
+
## INSTALL:
|
17
|
+
|
18
|
+
$ gem install patience_diff
|
19
|
+
|
20
|
+
## USAGE:
|
21
|
+
|
22
|
+
### Command line:
|
23
|
+
|
24
|
+
$ patience_diff [options] file-a file-b
|
25
|
+
|
26
|
+
Run with `--help` to see available options.
|
27
|
+
|
28
|
+
### Programmatically:
|
29
|
+
|
30
|
+
left = File.read("/path/to/old").split($RS)
|
31
|
+
left_timestamp = File.mtime("/path/to/old")
|
32
|
+
right = File.read("/path/to/new").split($RS)
|
33
|
+
right_timestamp = File.mtime("/path/to/new")
|
34
|
+
|
35
|
+
differ = PatienceDiff::UnifiedDiffer.new(:context => 10)
|
36
|
+
puts differ.diff(left, right, left_file, right_file, left_timestamp, right_timestamp)
|
37
|
+
|
38
|
+
## LICENSE:
|
39
|
+
|
40
|
+
(The MIT License)
|
41
|
+
|
42
|
+
Copyright (c) 2012 Andrew Watt
|
43
|
+
|
44
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
45
|
+
a copy of this software and associated documentation files (the
|
46
|
+
'Software'), to deal in the Software without restriction, including
|
47
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
48
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
49
|
+
permit persons to whom the Software is furnished to do so, subject to
|
50
|
+
the following conditions:
|
51
|
+
|
52
|
+
The above copyright notice and this permission notice shall be
|
53
|
+
included in all copies or substantial portions of the Software.
|
54
|
+
|
55
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
56
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
57
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
58
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
59
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
60
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
61
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/bin/patience_diff
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'English'
|
3
|
+
require 'trollop'
|
4
|
+
|
5
|
+
lib_path = File.expand_path(File.join(File.dirname(__FILE__),"..","lib"))
|
6
|
+
$LOAD_PATH.unshift(lib_path)
|
7
|
+
require 'patience_diff'
|
8
|
+
|
9
|
+
begin
|
10
|
+
opts = Trollop::options do
|
11
|
+
banner <<-EOF
|
12
|
+
Usage: #{File.basename($0)} [options] left-file right-file
|
13
|
+
Options:
|
14
|
+
EOF
|
15
|
+
version "patience_diff #{PatienceDiff::VERSION}"
|
16
|
+
opt :debug, "Debugging mode"
|
17
|
+
opt :context, "Lines of context", :default => 3
|
18
|
+
opt :full_context, "Don't collapse common sections; output entire files"
|
19
|
+
opt :ignore_whitespace,
|
20
|
+
"Ignore trailing whitespace, and treat leading whitespace as either present or not. This switch is for compatibility with diff's -b option.",
|
21
|
+
:short => '-b'
|
22
|
+
end
|
23
|
+
|
24
|
+
raise PatienceDiff::UsageError unless ARGV.length == 2
|
25
|
+
|
26
|
+
left_file, right_file = *ARGV
|
27
|
+
|
28
|
+
left = File.read(left_file, :external_encoding => Encoding::BINARY).split($RS)
|
29
|
+
left_timestamp = File.mtime(left_file)
|
30
|
+
right = File.read(right_file, :external_encoding => Encoding::BINARY).split($RS)
|
31
|
+
right_timestamp = File.mtime(right_file)
|
32
|
+
|
33
|
+
opts[:no_grouping] = opts[:full_context]
|
34
|
+
formatter = PatienceDiff::UnifiedDiffer.new(opts)
|
35
|
+
puts formatter.diff(left, right, left_file, right_file, left_timestamp, right_timestamp)
|
36
|
+
|
37
|
+
rescue PatienceDiff::UsageError => e
|
38
|
+
Trollop.module_eval do
|
39
|
+
@last_parser.educate($stderr)
|
40
|
+
end
|
41
|
+
rescue StandardError => e
|
42
|
+
if opts[:debug]
|
43
|
+
raise
|
44
|
+
else
|
45
|
+
$stderr.puts "Error: #{e}"
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'patience_diff/card'
|
2
|
+
|
3
|
+
module PatienceDiff
|
4
|
+
class SequenceMatcher
|
5
|
+
attr_accessor :context
|
6
|
+
|
7
|
+
def initialize(opts = {})
|
8
|
+
@context = opts[:context] || 3
|
9
|
+
end
|
10
|
+
|
11
|
+
# Generate a diff of a and b using #diff_opcodes, and split the opcode into groups
|
12
|
+
# whenever an :equal range is encountered that is longer than @context * 2.
|
13
|
+
# Returns an array of arrays of 5-tuples as described for #diff_opcodes.
|
14
|
+
def grouped_opcodes(a, b)
|
15
|
+
groups = []
|
16
|
+
last_group = []
|
17
|
+
diff_opcodes(a, b).each do |opcode|
|
18
|
+
if opcode[0] == :equal
|
19
|
+
if @context.zero?
|
20
|
+
groups << last_group
|
21
|
+
last_group = []
|
22
|
+
next
|
23
|
+
end
|
24
|
+
|
25
|
+
code, a_start, a_end, b_start, b_end = *opcode
|
26
|
+
|
27
|
+
if (a_start.zero? and b_start.zero?) or (a_end == a.length-1 and b_end == b.length-1)
|
28
|
+
threshold = @context
|
29
|
+
else
|
30
|
+
threshold = @context * 2
|
31
|
+
end
|
32
|
+
|
33
|
+
if (b_end - b_start + 1) > threshold
|
34
|
+
unless last_group.empty?
|
35
|
+
last_group << [
|
36
|
+
code,
|
37
|
+
a_start,
|
38
|
+
a_start + @context - 1,
|
39
|
+
b_start,
|
40
|
+
b_start + @context - 1
|
41
|
+
]
|
42
|
+
groups << last_group
|
43
|
+
last_group = []
|
44
|
+
end
|
45
|
+
opcode = [
|
46
|
+
code,
|
47
|
+
a_end - @context + 1,
|
48
|
+
a_end,
|
49
|
+
b_end - @context + 1,
|
50
|
+
b_end
|
51
|
+
]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
last_group << opcode
|
55
|
+
end
|
56
|
+
groups << last_group unless last_group.one? and last_group.first[0] == :equal
|
57
|
+
groups
|
58
|
+
end
|
59
|
+
|
60
|
+
# Generate a diff of a and b, and return an array of opcodes describing that diff.
|
61
|
+
# Each opcode represents a range in a and b that is either equal, only in a,
|
62
|
+
# or only in b. Opcodes are 5-tuples, in the format:
|
63
|
+
# 0: code
|
64
|
+
# A symbol indicating the diff operation. Can be :equal, :delete, or :insert.
|
65
|
+
# 1: a_start
|
66
|
+
# Index in a where the range begins
|
67
|
+
# 2: a_end
|
68
|
+
# Index in a where the range ends.
|
69
|
+
# 3: b_start
|
70
|
+
# Index in b where the range begins
|
71
|
+
# 4: b_end
|
72
|
+
# Index in b where the range ends.
|
73
|
+
#
|
74
|
+
# For :equal, (a_end - a_start) == (b_end - b_start).
|
75
|
+
# For :delete, a_start == a_end.
|
76
|
+
# For :insert, b_start == b_end.
|
77
|
+
def diff_opcodes(a, b)
|
78
|
+
sequences = collapse_matches(match(a, b))
|
79
|
+
sequences << [a.length, b.length, 0]
|
80
|
+
|
81
|
+
a_pos = b_pos = 0
|
82
|
+
opcodes = []
|
83
|
+
sequences.each do |(i, j, len)|
|
84
|
+
if a_pos < i
|
85
|
+
opcodes << [:delete, a_pos, i-1, b_pos, b_pos]
|
86
|
+
end
|
87
|
+
if b_pos < j
|
88
|
+
opcodes << [:insert, a_pos, a_pos, b_pos, j-1]
|
89
|
+
end
|
90
|
+
if len > 0
|
91
|
+
opcodes << [:equal, i, i+len-1, j, j+len-1]
|
92
|
+
end
|
93
|
+
a_pos = i+len
|
94
|
+
b_pos = j+len
|
95
|
+
end
|
96
|
+
opcodes
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def match(a, b)
|
101
|
+
matches = []
|
102
|
+
recursively_match(a, b, 0, 0, a.length, b.length) do |match|
|
103
|
+
matches << match
|
104
|
+
end
|
105
|
+
matches
|
106
|
+
end
|
107
|
+
|
108
|
+
def recursively_match(a, b, a_lo, b_lo, a_hi, b_hi)
|
109
|
+
return if a_lo == a_hi or b_lo == b_hi
|
110
|
+
|
111
|
+
last_a_pos = a_lo - 1
|
112
|
+
last_b_pos = b_lo - 1
|
113
|
+
|
114
|
+
longest_unique_subsequence(a[a_lo...a_hi], b[b_lo...b_hi]).each do |(a_pos, b_pos)|
|
115
|
+
# recurse betwen unique lines
|
116
|
+
a_pos += a_lo
|
117
|
+
b_pos += b_lo
|
118
|
+
if (last_a_pos+1 != a_pos) or (last_b_pos+1 != b_pos)
|
119
|
+
recursively_match(a, b, last_a_pos+1, last_b_pos+1, a_pos, b_pos) { |match| yield match }
|
120
|
+
end
|
121
|
+
last_a_pos = a_pos
|
122
|
+
last_b_pos = b_pos
|
123
|
+
yield [a_pos, b_pos]
|
124
|
+
end
|
125
|
+
|
126
|
+
if last_a_pos >= a_lo or last_b_pos >= b_lo
|
127
|
+
# there was at least one match
|
128
|
+
# recurse between last match and end
|
129
|
+
recursively_match(a, b, last_a_pos+1, last_b_pos+1, a_hi, b_hi) { |match| yield match }
|
130
|
+
elsif a[a_lo] == b[b_lo]
|
131
|
+
# no unique lines
|
132
|
+
# diff forward from beginning
|
133
|
+
while a_lo < a_hi and b_lo < b_hi and a[a_lo] == b[b_lo]
|
134
|
+
yield [a_lo, b_lo]
|
135
|
+
a_lo += 1
|
136
|
+
b_lo += 1
|
137
|
+
end
|
138
|
+
recursively_match(a, b, a_lo, b_lo, a_hi, b_hi) { |match| yield match }
|
139
|
+
elsif a[a_hi-1] == b[b_hi-1]
|
140
|
+
# no unique lines
|
141
|
+
# diff back from end
|
142
|
+
a_mid = a_hi - 1
|
143
|
+
b_mid = b_hi - 1
|
144
|
+
while a_mid > a_lo and b_mid > b_lo and a[a_mid-1] == b[b_mid-1]
|
145
|
+
a_mid -= 1
|
146
|
+
b_mid -= 1
|
147
|
+
end
|
148
|
+
recursively_match(a, b, a_lo, b_lo, a_mid, b_mid) { |match| yield match }
|
149
|
+
0...(a_hi-a_mid).each do |i|
|
150
|
+
yield [a_mid+i, b_mid+i]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def collapse_matches(matches)
|
156
|
+
return [] if matches.empty?
|
157
|
+
sequences = []
|
158
|
+
start_a, start_b = *(matches.first)
|
159
|
+
len = 1
|
160
|
+
matches[1..-1].each do |(i_a, i_b)|
|
161
|
+
if i_a == start_a + len and i_b == start_b + len
|
162
|
+
len += 1
|
163
|
+
else
|
164
|
+
sequences << [start_a, start_b, len]
|
165
|
+
start_a = i_a
|
166
|
+
start_b = i_b
|
167
|
+
len = 1
|
168
|
+
end
|
169
|
+
end
|
170
|
+
sequences << [start_a, start_b, len]
|
171
|
+
sequences
|
172
|
+
end
|
173
|
+
|
174
|
+
def longest_unique_subsequence(a, b)
|
175
|
+
deck = Array.new(b.length)
|
176
|
+
unique_a = {}
|
177
|
+
unique_b = {}
|
178
|
+
|
179
|
+
a.each_with_index do |val, index|
|
180
|
+
if unique_a.has_key? val
|
181
|
+
unique_a[val] = nil
|
182
|
+
else
|
183
|
+
unique_a[val] = index
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
b.each_with_index do |val, index|
|
188
|
+
a_index = unique_a[val]
|
189
|
+
next unless a_index
|
190
|
+
dupe_index = unique_b[val]
|
191
|
+
if dupe_index
|
192
|
+
deck[dupe_index] = nil
|
193
|
+
unique_a.delete(val)
|
194
|
+
else
|
195
|
+
unique_b[val] = index
|
196
|
+
deck[index] = a_index
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
card = patience_sort(deck).last
|
201
|
+
result = []
|
202
|
+
while card
|
203
|
+
result.unshift [card.value, card.index]
|
204
|
+
card = card.previous
|
205
|
+
end
|
206
|
+
result
|
207
|
+
end
|
208
|
+
|
209
|
+
def patience_sort(deck)
|
210
|
+
piles = []
|
211
|
+
pile = 0
|
212
|
+
deck.each_with_index do |card_value, index|
|
213
|
+
next if card_value.nil?
|
214
|
+
card = Card.new(index, card_value)
|
215
|
+
|
216
|
+
if piles.any? and piles.last.value < card_value
|
217
|
+
pile = piles.size
|
218
|
+
elsif piles.any? and piles[pile].value < card_value and
|
219
|
+
(pile == piles.size-1 or piles[pile+1].value > card_value)
|
220
|
+
pile += 1
|
221
|
+
else
|
222
|
+
pile = bisect(piles, card_value)
|
223
|
+
end
|
224
|
+
|
225
|
+
card.previous = piles[pile-1] if pile > 0
|
226
|
+
|
227
|
+
if pile < piles.size
|
228
|
+
#puts "putting card #{card.value} on pile #{pile}"
|
229
|
+
piles[pile] = card
|
230
|
+
else
|
231
|
+
#puts "putting card #{card.value} on new pile"
|
232
|
+
piles << card
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
piles
|
237
|
+
end
|
238
|
+
|
239
|
+
def bisect(piles, target)
|
240
|
+
low = 0
|
241
|
+
high = piles.size - 1
|
242
|
+
while (low <= high)
|
243
|
+
mid = (low + high)/2
|
244
|
+
if piles[mid].value < target
|
245
|
+
low = mid + 1
|
246
|
+
else
|
247
|
+
high = mid - 1
|
248
|
+
end
|
249
|
+
end
|
250
|
+
low
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,66 @@
|
|
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
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: patience_diff
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Watt
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: trollop
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.16'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
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
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rdoc
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.10'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.10'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: hoe
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '3.0'
|
78
|
+
description: ! 'A Ruby implementation of the Patience diff algorithm.
|
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].
|
84
|
+
|
85
|
+
|
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
|
+
|
90
|
+
|
91
|
+
[example]: http://alfedenzo.livejournal.com/170301.html
|
92
|
+
|
93
|
+
[bazaar]: http://bazaar.canonical.com/'
|
94
|
+
email:
|
95
|
+
- andrew@wattornot.com
|
96
|
+
executables:
|
97
|
+
- patience_diff
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files:
|
100
|
+
- History.txt
|
101
|
+
- Manifest.txt
|
102
|
+
files:
|
103
|
+
- History.txt
|
104
|
+
- Manifest.txt
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- bin/patience_diff
|
108
|
+
- lib/patience_diff.rb
|
109
|
+
- lib/patience_diff/card.rb
|
110
|
+
- lib/patience_diff/sequence_matcher.rb
|
111
|
+
- lib/patience_diff/unified_differ.rb
|
112
|
+
- lib/patience_diff/usage_error.rb
|
113
|
+
homepage: http://github.com/watt/ruby_patience_diff
|
114
|
+
licenses: []
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options:
|
117
|
+
- --main
|
118
|
+
- README.md
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project: patiencediff
|
135
|
+
rubygems_version: 1.8.24
|
136
|
+
signing_key:
|
137
|
+
specification_version: 3
|
138
|
+
summary: A Ruby implementation of the Patience diff algorithm
|
139
|
+
test_files: []
|