diff-lcs 1.1.3 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +3 -0
- data/.rspec +2 -0
- data/.travis.yml +21 -0
- data/Contributing.rdoc +63 -0
- data/Gemfile +20 -0
- data/History.rdoc +37 -1
- data/Manifest.txt +10 -1
- data/README.rdoc +48 -23
- data/Rakefile +17 -3
- data/autotest/discover.rb +1 -0
- data/diff-lcs.gemspec +41 -29
- data/docs/COPYING.txt +21 -22
- data/docs/artistic.txt +127 -0
- data/lib/diff-lcs.rb +0 -2
- data/lib/diff/lcs.rb +654 -954
- data/lib/diff/lcs/array.rb +1 -15
- data/lib/diff/lcs/block.rb +4 -18
- data/lib/diff/lcs/callbacks.rb +222 -222
- data/lib/diff/lcs/change.rb +110 -102
- data/lib/diff/lcs/htmldiff.rb +0 -2
- data/lib/diff/lcs/hunk.rb +65 -56
- data/lib/diff/lcs/internals.rb +300 -0
- data/lib/diff/lcs/ldiff.rb +154 -169
- data/lib/diff/lcs/string.rb +1 -15
- data/spec/change_spec.rb +65 -0
- data/spec/diff_spec.rb +8 -2
- data/spec/issues_spec.rb +24 -0
- data/spec/lcs_spec.rb +23 -5
- data/spec/patch_spec.rb +51 -27
- data/spec/sdiff_spec.rb +0 -2
- data/spec/spec_helper.rb +9 -3
- data/spec/traverse_balanced_spec.rb +26 -2
- data/spec/traverse_sequences_spec.rb +84 -28
- metadata +219 -83
- data/docs/artistic.html +0 -289
data/lib/diff/lcs/change.rb
CHANGED
@@ -1,59 +1,58 @@
|
|
1
|
-
|
2
|
-
#--
|
3
|
-
# Copyright 2004 Austin Ziegler <diff-lcs@halostatue.ca>
|
4
|
-
# adapted from:
|
5
|
-
# Algorithm::Diff (Perl) by Ned Konz <perl@bike-nomad.com>
|
6
|
-
# Smalltalk by Mario I. Wolczko <mario@wolczko.com>
|
7
|
-
# implements McIlroy-Hunt diff algorithm
|
8
|
-
#
|
9
|
-
# This program is free software. It may be redistributed and/or modified under
|
10
|
-
# the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
|
11
|
-
# Ruby licence.
|
12
|
-
#
|
13
|
-
# $Id$
|
14
|
-
#++
|
15
|
-
# Provides Diff::LCS::Change and Diff::LCS::ContextChange.
|
16
|
-
|
17
|
-
# Centralises the change test code in Diff::LCS::Change and
|
18
|
-
# Diff::LCS::ContextChange, since it's the same for both classes.
|
19
|
-
module Diff::LCS::ChangeTypeTests
|
20
|
-
def deleting?
|
21
|
-
@action == '-'
|
22
|
-
end
|
1
|
+
# -*- ruby encoding: utf-8 -*-
|
23
2
|
|
24
|
-
|
25
|
-
|
26
|
-
|
3
|
+
# Represents a simplistic (non-contextual) change. Represents the removal or
|
4
|
+
# addition of an element from either the old or the new sequenced
|
5
|
+
# enumerable.
|
6
|
+
class Diff::LCS::Change
|
7
|
+
# The only actions valid for changes are '+' (add), '-' (delete), '='
|
8
|
+
# (no change), '!' (changed), '<' (tail changes from first sequence), or
|
9
|
+
# '>' (tail changes from second sequence). The last two ('<>') are only
|
10
|
+
# found with Diff::LCS::diff and Diff::LCS::sdiff.
|
11
|
+
VALID_ACTIONS = %W(+ - = ! > <)
|
27
12
|
|
28
|
-
def
|
29
|
-
|
13
|
+
def self.valid_action?(action)
|
14
|
+
VALID_ACTIONS.include? action
|
30
15
|
end
|
31
16
|
|
32
|
-
|
33
|
-
|
17
|
+
# Returns the action this Change represents.
|
18
|
+
attr_reader :action
|
19
|
+
|
20
|
+
# Returns the position of the Change.
|
21
|
+
attr_reader :position
|
22
|
+
# Returns the sequence element of the Change.
|
23
|
+
attr_reader :element
|
24
|
+
|
25
|
+
def initialize(*args)
|
26
|
+
@action, @position, @element = *args
|
27
|
+
|
28
|
+
unless Diff::LCS::Change.valid_action?(@action)
|
29
|
+
raise "Invalid Change Action '#{@action}'"
|
30
|
+
end
|
31
|
+
raise "Invalid Position Type" unless @position.kind_of? Fixnum
|
34
32
|
end
|
35
33
|
|
36
|
-
def
|
37
|
-
|
34
|
+
def inspect
|
35
|
+
to_a.inspect
|
38
36
|
end
|
39
37
|
|
40
|
-
def
|
41
|
-
@
|
38
|
+
def to_a
|
39
|
+
[ @action, @position, @element ]
|
42
40
|
end
|
43
|
-
end
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
42
|
+
def self.from_a(arr)
|
43
|
+
arr = arr.flatten
|
44
|
+
case arr.size
|
45
|
+
when 5
|
46
|
+
Diff::LCS::ContextChange.new(*(arr[0...5]))
|
47
|
+
when 3
|
48
|
+
Diff::LCS::Change.new(*(arr[0...3]))
|
49
|
+
else
|
50
|
+
raise "Invalid change array format provided."
|
51
|
+
end
|
52
|
+
end
|
55
53
|
|
56
54
|
include Comparable
|
55
|
+
|
57
56
|
def ==(other)
|
58
57
|
(self.action == other.action) and
|
59
58
|
(self.position == other.position) and
|
@@ -67,85 +66,79 @@ class Diff::LCS::Change
|
|
67
66
|
r
|
68
67
|
end
|
69
68
|
|
70
|
-
def
|
71
|
-
@action
|
72
|
-
@position = position
|
73
|
-
@element = element
|
69
|
+
def adding?
|
70
|
+
@action == '+'
|
74
71
|
end
|
75
72
|
|
76
|
-
|
77
|
-
|
78
|
-
[@action, @position, @element]
|
73
|
+
def deleting?
|
74
|
+
@action == '-'
|
79
75
|
end
|
80
76
|
|
81
|
-
def
|
82
|
-
|
77
|
+
def unchanged?
|
78
|
+
@action == '='
|
79
|
+
end
|
80
|
+
|
81
|
+
def changed?
|
82
|
+
@action == '!'
|
83
83
|
end
|
84
84
|
|
85
|
-
|
85
|
+
def finished_a?
|
86
|
+
@action == '>'
|
87
|
+
end
|
88
|
+
|
89
|
+
def finished_b?
|
90
|
+
@action == '<'
|
91
|
+
end
|
86
92
|
end
|
87
93
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
class Diff::LCS::ContextChange
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
# Represents a contextual change. Contains the position and values of the
|
95
|
+
# elements in the old and the new sequenced enumerables as well as the action
|
96
|
+
# taken.
|
97
|
+
class Diff::LCS::ContextChange < Diff::LCS::Change
|
98
|
+
# We don't need these two values.
|
99
|
+
undef :position
|
100
|
+
undef :element
|
101
|
+
|
102
|
+
# Returns the old position being changed.
|
97
103
|
attr_reader :old_position
|
98
|
-
|
104
|
+
# Returns the new position being changed.
|
99
105
|
attr_reader :new_position
|
106
|
+
# Returns the old element being changed.
|
107
|
+
attr_reader :old_element
|
108
|
+
# Returns the new element being changed.
|
100
109
|
attr_reader :new_element
|
101
110
|
|
102
|
-
|
103
|
-
|
104
|
-
def ==(other)
|
105
|
-
(@action == other.action) and
|
106
|
-
(@old_position == other.old_position) and
|
107
|
-
(@new_position == other.new_position) and
|
108
|
-
(@old_element == other.old_element) and
|
109
|
-
(@new_element == other.new_element)
|
110
|
-
end
|
111
|
+
def initialize(*args)
|
112
|
+
@action, @old_position, @old_element, @new_position, @new_element = *args
|
111
113
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
r = @new_element <=> other.new_element if r.zero?
|
122
|
-
r
|
114
|
+
unless Diff::LCS::Change.valid_action?(@action)
|
115
|
+
raise "Invalid Change Action '#{@action}'"
|
116
|
+
end
|
117
|
+
unless @old_position.nil? or @old_position.kind_of? Fixnum
|
118
|
+
raise "Invalid (Old) Position Type"
|
119
|
+
end
|
120
|
+
unless @new_position.nil? or @new_position.kind_of? Fixnum
|
121
|
+
raise "Invalid (New) Position Type"
|
122
|
+
end
|
123
123
|
end
|
124
124
|
|
125
|
-
def
|
126
|
-
@action
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
@new_element = new_element
|
125
|
+
def to_a
|
126
|
+
[ @action,
|
127
|
+
[ @old_position, @old_element ],
|
128
|
+
[ @new_position, @new_element ]
|
129
|
+
]
|
131
130
|
end
|
132
131
|
|
133
|
-
def
|
134
|
-
|
132
|
+
def inspect(*args)
|
133
|
+
to_a.inspect
|
135
134
|
end
|
136
135
|
|
137
|
-
# Creates a ContextChange from an array produced by ContextChange#to_a.
|
138
136
|
def self.from_a(arr)
|
139
|
-
|
140
|
-
Diff::LCS::ContextChange.new(arr[0], arr[1], arr[2], arr[3], arr[4])
|
141
|
-
else
|
142
|
-
Diff::LCS::ContextChange.new(arr[0], arr[1][0], arr[1][1], arr[2][0],
|
143
|
-
arr[2][1])
|
144
|
-
end
|
137
|
+
Diff::LCS::Change.from_a(arr)
|
145
138
|
end
|
146
139
|
|
147
|
-
|
148
|
-
|
140
|
+
# Simplifies a context change for use in some diff callbacks. '<' actions
|
141
|
+
# are converted to '-' and '>' actions are converted to '+'.
|
149
142
|
def self.simplify(event)
|
150
143
|
ea = event.to_a
|
151
144
|
|
@@ -165,5 +158,20 @@ class Diff::LCS::ContextChange
|
|
165
158
|
Diff::LCS::ContextChange.from_a(ea)
|
166
159
|
end
|
167
160
|
|
168
|
-
|
161
|
+
def ==(other)
|
162
|
+
(@action == other.action) and
|
163
|
+
(@old_position == other.old_position) and
|
164
|
+
(@new_position == other.new_position) and
|
165
|
+
(@old_element == other.old_element) and
|
166
|
+
(@new_element == other.new_element)
|
167
|
+
end
|
168
|
+
|
169
|
+
def <=>(other)
|
170
|
+
r = @action <=> other.action
|
171
|
+
r = @old_position <=> other.old_position if r.zero?
|
172
|
+
r = @new_position <=> other.new_position if r.zero?
|
173
|
+
r = @old_element <=> other.old_element if r.zero?
|
174
|
+
r = @new_element <=> other.new_element if r.zero?
|
175
|
+
r
|
176
|
+
end
|
169
177
|
end
|
data/lib/diff/lcs/htmldiff.rb
CHANGED
data/lib/diff/lcs/hunk.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
# -*- ruby encoding: utf-8 -*-
|
2
|
+
|
1
3
|
require 'diff/lcs/block'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
# A Hunk is a group of Blocks which overlap because of the context
|
6
|
+
# surrounding each block. (So if we're not using context, every hunk will
|
7
|
+
# contain one block.) Used in the diff program (bin/diff).
|
6
8
|
class Diff::LCS::Hunk
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(data_old, data_new, piece,
|
10
|
-
|
9
|
+
# Create a hunk using references to both the old and new data, as well as
|
10
|
+
# the piece of data.
|
11
|
+
def initialize(data_old, data_new, piece, flag_context, file_length_difference)
|
12
|
+
# At first, a hunk will have just one Block in it
|
11
13
|
@blocks = [ Diff::LCS::Block.new(piece) ]
|
12
14
|
@data_old = data_old
|
13
15
|
@data_new = data_new
|
@@ -16,10 +18,10 @@ class Diff::LCS::Hunk
|
|
16
18
|
after += @blocks[0].diff_size
|
17
19
|
@file_length_difference = after # The caller must get this manually
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
# Save the start & end of each array. If the array doesn't exist (e.g.,
|
22
|
+
# we're only adding items in this block), then figure out the line
|
23
|
+
# number based on the line number of the other file and the current
|
24
|
+
# difference in file lengths.
|
23
25
|
if @blocks[0].remove.empty?
|
24
26
|
a1 = a2 = nil
|
25
27
|
else
|
@@ -39,7 +41,7 @@ class Diff::LCS::Hunk
|
|
39
41
|
@end_old = a2 || (b2 - after)
|
40
42
|
@end_new = b2 || (a2 + after)
|
41
43
|
|
42
|
-
self.flag_context =
|
44
|
+
self.flag_context = flag_context
|
43
45
|
end
|
44
46
|
|
45
47
|
attr_reader :blocks
|
@@ -47,10 +49,10 @@ class Diff::LCS::Hunk
|
|
47
49
|
attr_reader :end_old, :end_new
|
48
50
|
attr_reader :file_length_difference
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
+
# Change the "start" and "end" fields to note that context should be added
|
53
|
+
# to this hunk.
|
52
54
|
attr_accessor :flag_context
|
53
|
-
undef :flag_context
|
55
|
+
undef :flag_context=;
|
54
56
|
def flag_context=(context) #:nodoc:
|
55
57
|
return if context.nil? or context.zero?
|
56
58
|
|
@@ -67,22 +69,28 @@ class Diff::LCS::Hunk
|
|
67
69
|
@end_new += add_end
|
68
70
|
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
# Merges this hunk and the provided hunk together if they overlap. Returns
|
73
|
+
# a truthy value so that if there is no overlap, you can know the merge
|
74
|
+
# was skipped.
|
75
|
+
def merge(hunk)
|
76
|
+
if overlaps?(hunk)
|
77
|
+
@start_old = hunk.start_old
|
78
|
+
@start_new = hunk.start_new
|
79
|
+
blocks.unshift(*hunk.blocks)
|
80
|
+
else
|
81
|
+
nil
|
82
|
+
end
|
74
83
|
end
|
75
84
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
b = (@start_new - hunk.end_new) <= 1
|
83
|
-
return (a or b)
|
85
|
+
# Determines whether there is an overlap between this hunk and the
|
86
|
+
# provided hunk. This will be true if the difference between the two hunks
|
87
|
+
# start or end positions is within one position of each other.
|
88
|
+
def overlaps?(hunk)
|
89
|
+
hunk and (((@start_old - hunk.end_old) <= 1) or
|
90
|
+
((@start_new - hunk.end_new) <= 1))
|
84
91
|
end
|
85
92
|
|
93
|
+
# Returns a diff string based on a format.
|
86
94
|
def diff(format)
|
87
95
|
case format
|
88
96
|
when :old
|
@@ -100,44 +108,40 @@ class Diff::LCS::Hunk
|
|
100
108
|
end
|
101
109
|
end
|
102
110
|
|
103
|
-
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
private
|
108
|
-
# Note that an old diff can't have any context. Therefore, we know that
|
109
|
-
# there's only one block in the hunk.
|
111
|
+
# Note that an old diff can't have any context. Therefore, we know that
|
112
|
+
# there's only one block in the hunk.
|
110
113
|
def old_diff
|
111
114
|
warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
|
112
115
|
op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
|
113
116
|
|
114
117
|
block = @blocks[0]
|
115
118
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
+
# Calculate item number range. Old diff range is just like a context
|
120
|
+
# diff range, except the ranges are on one line with the action between
|
121
|
+
# them.
|
119
122
|
s = "#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n"
|
120
|
-
|
121
|
-
|
123
|
+
# If removing anything, just print out all the remove lines in the hunk
|
124
|
+
# which is just all the remove lines in the block.
|
122
125
|
@data_old[@start_old .. @end_old].each { |e| s << "< #{e}\n" } unless block.remove.empty?
|
123
126
|
s << "---\n" if block.op == "!"
|
124
127
|
@data_new[@start_new .. @end_new].each { |e| s << "> #{e}\n" } unless block.insert.empty?
|
125
128
|
s
|
126
129
|
end
|
130
|
+
private :old_diff
|
127
131
|
|
128
132
|
def unified_diff
|
129
|
-
|
133
|
+
# Calculate item number range.
|
130
134
|
s = "@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n"
|
131
135
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
136
|
+
# Outlist starts containing the hunk of the old file. Removing an item
|
137
|
+
# just means putting a '-' in front of it. Inserting an item requires
|
138
|
+
# getting it from the new file and splicing it in. We splice in
|
139
|
+
# +num_added+ items. Remove blocks use +num_added+ because splicing
|
140
|
+
# changed the length of outlist.
|
141
|
+
#
|
142
|
+
# We remove +num_removed+ items. Insert blocks use +num_removed+
|
143
|
+
# because their item numbers -- corresponding to positions in the NEW
|
144
|
+
# file -- don't take removed items into account.
|
141
145
|
lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0
|
142
146
|
|
143
147
|
outlist = @data_old[lo .. hi].collect { |e| e.gsub(/^/, ' ') }
|
@@ -159,14 +163,15 @@ class Diff::LCS::Hunk
|
|
159
163
|
|
160
164
|
s << outlist.join("\n")
|
161
165
|
end
|
166
|
+
private :unified_diff
|
162
167
|
|
163
168
|
def context_diff
|
164
169
|
s = "***************\n"
|
165
170
|
s << "*** #{context_range(:old)} ****\n"
|
166
171
|
r = context_range(:new)
|
167
172
|
|
168
|
-
|
169
|
-
|
173
|
+
# Print out file 1 part for each block in context diff format if there
|
174
|
+
# are any blocks that remove items
|
170
175
|
lo, hi = @start_old, @end_old
|
171
176
|
removes = @blocks.select { |e| not e.remove.empty? }
|
172
177
|
if removes
|
@@ -193,6 +198,7 @@ class Diff::LCS::Hunk
|
|
193
198
|
end
|
194
199
|
s
|
195
200
|
end
|
201
|
+
private :context_diff
|
196
202
|
|
197
203
|
def ed_diff(format)
|
198
204
|
op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
|
@@ -210,9 +216,10 @@ class Diff::LCS::Hunk
|
|
210
216
|
end
|
211
217
|
s
|
212
218
|
end
|
219
|
+
private :ed_diff
|
213
220
|
|
214
|
-
|
215
|
-
|
221
|
+
# Generate a range of item numbers to print. Only print 1 number if the
|
222
|
+
# range has only one item in it. Otherwise, it's 'start,end'
|
216
223
|
def context_range(mode)
|
217
224
|
case mode
|
218
225
|
when :old
|
@@ -223,10 +230,11 @@ class Diff::LCS::Hunk
|
|
223
230
|
|
224
231
|
(s < e) ? "#{s},#{e}" : "#{e}"
|
225
232
|
end
|
233
|
+
private :context_range
|
226
234
|
|
227
|
-
|
228
|
-
|
229
|
-
|
235
|
+
# Generate a range of item numbers to print for unified diff. Print number
|
236
|
+
# where block starts, followed by number of lines in the block
|
237
|
+
# (don't print number of lines if it's 1)
|
230
238
|
def unified_range(mode)
|
231
239
|
case mode
|
232
240
|
when :old
|
@@ -239,4 +247,5 @@ class Diff::LCS::Hunk
|
|
239
247
|
first = (length < 2) ? e : s # "strange, but correct"
|
240
248
|
(length == 1) ? "#{first}" : "#{first},#{length}"
|
241
249
|
end
|
250
|
+
private :unified_range
|
242
251
|
end
|