diff-lcs 1.1.1

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.
@@ -0,0 +1,257 @@
1
+ #! /usr/env/bin ruby
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: hunk.rb,v 1.2 2004/08/08 20:33:09 austin Exp $
14
+ #++
15
+ # Contains Diff::LCS::Hunk for bin/ldiff.
16
+
17
+ require 'diff/lcs/block'
18
+
19
+ # A Hunk is a group of Blocks which overlap because of the context
20
+ # surrounding each block. (So if we're not using context, every hunk will
21
+ # contain one block.) Used in the diff program (bin/diff).
22
+ class Diff::LCS::Hunk
23
+ # Create a hunk using references to both the old and new data, as well as
24
+ # the piece of data
25
+ def initialize(data_old, data_new, piece, context, file_length_difference)
26
+ # At first, a hunk will have just one Block in it
27
+ @blocks = [ Diff::LCS::Block.new(piece) ]
28
+ @data_old = data_old
29
+ @data_new = data_new
30
+
31
+ before = after = file_length_difference
32
+ after += @blocks[0].diff_size
33
+ @file_length_difference = after # The caller must get this manually
34
+
35
+ # Save the start & end of each array. If the array doesn't exist
36
+ # (e.g., we're only adding items in this block), then figure out the
37
+ # line number based on the line number of the other file and the
38
+ # current difference in file lengths.
39
+ if @blocks[0].remove.empty?
40
+ a1 = a2 = nil
41
+ else
42
+ a1 = @blocks[0].remove[0].position
43
+ a2 = @blocks[0].remove[-1].position
44
+ end
45
+
46
+ if @blocks[0].insert.empty?
47
+ b1 = b2 = nil
48
+ else
49
+ b1 = @blocks[0].insert[0].position
50
+ b2 = @blocks[0].insert[-1].position
51
+ end
52
+
53
+ @start_old = a1 || (b1 - before)
54
+ @start_new = b1 || (a1 + before)
55
+ @end_old = a2 || (b2 - after)
56
+ @end_new = b2 || (a2 + after)
57
+
58
+ self.flag_context = context
59
+ end
60
+
61
+ attr_reader :blocks
62
+ attr_reader :start_old, :start_new
63
+ attr_reader :end_old, :end_new
64
+ attr_reader :file_length_difference
65
+
66
+ # Change the "start" and "end" fields to note that context should be added
67
+ # to this hunk
68
+ attr_accessor :flag_context
69
+ def flag_context=(context) #:nodoc:
70
+ return if context.nil? or context.zero?
71
+
72
+ add_start = (context > @start_old) ? @start_old : context
73
+ @start_old -= add_start
74
+ @start_new -= add_start
75
+
76
+ if (@end_old + context) > @data_old.size
77
+ add_end = @data_old.size - @end_old
78
+ else
79
+ add_end = context
80
+ end
81
+ @end_old += add_end
82
+ @end_new += add_end
83
+ end
84
+
85
+ def unshift(hunk)
86
+ @start_old = hunk.start_old
87
+ @start_new = hunk.start_new
88
+ blocks.unshift(*hunk.blocks)
89
+ end
90
+
91
+ # Is there an overlap between hunk arg0 and old hunk arg1? Note: if end
92
+ # of old hunk is one less than beginning of second, they overlap
93
+ def overlaps?(hunk = nil)
94
+ return nil if hunk.nil?
95
+
96
+ a = (@start_old - hunk.end_old) <= 1
97
+ b = (@start_new - hunk.end_new) <= 1
98
+ return (a or b)
99
+ end
100
+
101
+ def diff(format)
102
+ case format
103
+ when :old
104
+ old_diff
105
+ when :unified
106
+ unified_diff
107
+ when :context
108
+ context_diff
109
+ when :ed
110
+ self
111
+ when :reverse_ed, :ed_finish
112
+ ed_diff(format)
113
+ else
114
+ raise "Unknown diff format #{format}."
115
+ end
116
+ end
117
+
118
+ def each_old(block)
119
+ @data_old[@start_old .. @end_old].each { |e| yield e }
120
+ end
121
+
122
+ private
123
+ # Note that an old diff can't have any context. Therefore, we know that
124
+ # there's only one block in the hunk.
125
+ def old_diff
126
+ warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
127
+ op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
128
+
129
+ block = @blocks[0]
130
+
131
+ # Calculate item number range. Old diff range is just like a context
132
+ # diff range, except the ranges are on one line with the action between
133
+ # them.
134
+ s = "#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n"
135
+ # If removing anything, just print out all the remove lines in the hunk
136
+ # which is just all the remove lines in the block.
137
+ @data_old[@start_old .. @end_old].each { |e| s << "< #{e}\n" } unless block.remove.empty?
138
+ s << "---\n" if block.op == "!"
139
+ @data_new[@start_new .. @end_new].each { |e| s << "> #{e}\n" } unless block.insert.empty?
140
+ s
141
+ end
142
+
143
+ def unified_diff
144
+ # Calculate item number range.
145
+ s = "@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n"
146
+
147
+ # Outlist starts containing the hunk of the old file. Removing an item
148
+ # just means putting a '-' in front of it. Inserting an item requires
149
+ # getting it from the new file and splicing it in. We splice in
150
+ # +num_added+ items. Remove blocks use +num_added+ because splicing
151
+ # changed the length of outlist.
152
+ #
153
+ # We remove +num_removed+ items. Insert blocks use +num_removed+
154
+ # because their item numbers -- corresponding to positions in the NEW
155
+ # file -- don't take removed items into account.
156
+ lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0
157
+
158
+ outlist = @data_old[lo .. hi].collect { |e| e.gsub(/^/, ' ') }
159
+
160
+ @blocks.each do |block|
161
+ block.remove.each do |item|
162
+ op = item.action.to_s # -
163
+ offset = item.position - lo + num_added
164
+ outlist[offset].gsub!(/^ /, op.to_s)
165
+ num_removed += 1
166
+ end
167
+ block.insert.each do |item|
168
+ op = item.action.to_s # +
169
+ offset = item.position - @start_new + num_removed
170
+ outlist[offset, 0] = "#{op}#{@data_new[item.position]}"
171
+ num_added += 1
172
+ end
173
+ end
174
+
175
+ s << outlist.join("\n")
176
+ end
177
+
178
+ def context_diff
179
+ s = "***************\n"
180
+ s << "*** #{context_range(:old)} ****\n"
181
+ r = context_range(:new)
182
+
183
+ # Print out file 1 part for each block in context diff format if there
184
+ # are any blocks that remove items
185
+ lo, hi = @start_old, @end_old
186
+ removes = @blocks.select { |e| not e.remove.empty? }
187
+ if removes
188
+ outlist = @data_old[lo .. hi].collect { |e| e.gsub(/^/, ' ') }
189
+ removes.each do |block|
190
+ block.remove.each do |item|
191
+ outlist[item.position - lo].gsub!(/^ /) { block.op } # - or !
192
+ end
193
+ end
194
+ s << outlist.join("\n")
195
+ end
196
+
197
+ s << "\n--- #{r} ----\n"
198
+ lo, hi = @start_new, @end_new
199
+ inserts = @blocks.select { |e| not e.insert.empty? }
200
+ if inserts
201
+ outlist = @data_new[lo .. hi].collect { |e| e.gsub(/^/, ' ') }
202
+ inserts.each do |block|
203
+ block.insert.each do |item|
204
+ outlist[item.position - lo].gsub!(/^ /) { block.op } # + or !
205
+ end
206
+ end
207
+ s << outlist.join("\n")
208
+ end
209
+ s
210
+ end
211
+
212
+ def ed_diff(format)
213
+ op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
214
+ warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
215
+
216
+ if format == :reverse_ed
217
+ s = "#{op_act[@blocks[0].op]}#{context_range(:old)}\n"
218
+ else
219
+ s = "#{context_range(:old).gsub(/,/, ' ')}#{op_act[@blocks[0].op]}\n"
220
+ end
221
+
222
+ unless @blocks[0].insert.empty?
223
+ @data_new[@start_new .. @end_new].each { |e| s << "#{e}\n" }
224
+ s << ".\n"
225
+ end
226
+ s
227
+ end
228
+
229
+ # Generate a range of item numbers to print. Only print 1 number if the
230
+ # range has only one item in it. Otherwise, it's 'start,end'
231
+ def context_range(mode)
232
+ case mode
233
+ when :old
234
+ s, e = (@start_old + 1), (@end_old + 1)
235
+ when :new
236
+ s, e = (@start_new + 1), (@end_new + 1)
237
+ end
238
+
239
+ (s < e) ? "#{s},#{e}" : "#{e}"
240
+ end
241
+
242
+ # Generate a range of item numbers to print for unified diff. Print
243
+ # number where block starts, followed by number of lines in the block
244
+ # (don't print number of lines if it's 1)
245
+ def unified_range(mode)
246
+ case mode
247
+ when :old
248
+ s, e = (@start_old + 1), (@end_old + 1)
249
+ when :new
250
+ s, e = (@start_new + 1), (@end_new + 1)
251
+ end
252
+
253
+ length = e - s + 1
254
+ first = (length < 2) ? e : s # "strange, but correct"
255
+ (length == 1) ? "#{first}" : "#{first},#{length}"
256
+ end
257
+ end
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'ostruct'
5
+ require 'diff/lcs/hunk'
6
+
7
+ # == ldiff Usage
8
+ # ldiff [options] oldfile newfile
9
+ #
10
+ # -c:: Displays a context diff with 3 lines of context.
11
+ # -C [LINES], --context [LINES]:: Displays a context diff with LINES lines of context. Default 3 lines.
12
+ # -u:: Displays a unified diff with 3 lines of context.
13
+ # -U [LINES], --unified [LINES]:: Displays a unified diff with LINES lines of context. Default 3 lines.
14
+ # -e:: Creates an 'ed' script to change oldfile to newfile.
15
+ # -f:: Creates an 'ed' script to change oldfile to newfile in reverse order.
16
+ # -a, --text:: Treats the files as text and compares them line-by-line, even if they do not seem to be text.
17
+ # --binary:: Treats the files as binary.
18
+ # -q, --brief:: Reports only whether or not the files differ, not the details.
19
+ # --help:: Shows the command-line help.
20
+ # --version:: Shows the version of Diff::LCS.
21
+ #
22
+ # By default, runs produces an "old-style" diff, with output like UNIX diff.
23
+ #
24
+ # == Copyright
25
+ # Copyright &copy; 2004 Austin Ziegler
26
+ #
27
+ # Part of Diff::LCS <http://rubyforge.org/projects/ruwiki/>
28
+ # Austin Ziegler <diff-lcs@halostatue.ca>
29
+ #
30
+ # This program is free software. It may be redistributed and/or modified under
31
+ # the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
32
+ # Ruby licence.
33
+ module Diff::LCS::Ldiff
34
+ BANNER = <<-COPYRIGHT
35
+ ldiff #{Diff::LCS::VERSION}
36
+ Copyright � 2004 Austin Ziegler
37
+
38
+ Part of Diff::LCS.
39
+ http://rubyforge.org/projects/ruwiki/
40
+
41
+ Austin Ziegler <diff-lcs@halostatue.ca>
42
+
43
+ This program is free software. It may be redistributed and/or modified under
44
+ the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
45
+ Ruby licence.
46
+
47
+ $Id: ldiff.rb,v 1.1 2004/09/26 01:37:49 austin Exp $
48
+ COPYRIGHT
49
+
50
+ class << self
51
+ attr_reader :format, :lines #:nodoc:
52
+ attr_reader :file_old, :file_new #:nodoc:
53
+ attr_reader :data_old, :data_new #:nodoc:
54
+
55
+ def run(args, input = $stdin, output = $stdout, error = $stderr) #:nodoc:
56
+ args.options do |o|
57
+ o.banner = "Usage: #{File.basename($0)} [options] oldfile newfile"
58
+ o.separator ""
59
+ o.on('-c',
60
+ 'Displays a context diff with 3 lines of',
61
+ 'context.') do |ctx|
62
+ @format = :context
63
+ @lines = 3
64
+ end
65
+ o.on('-C', '--context [LINES]', Numeric,
66
+ 'Displays a context diff with LINES lines',
67
+ 'of context. Default 3 lines.') do |ctx|
68
+ @format = :context
69
+ @lines = ctx || 3
70
+ end
71
+ o.on('-u',
72
+ 'Displays a unified diff with 3 lines of',
73
+ 'context.') do |ctx|
74
+ @format = :unified
75
+ @lines = 3
76
+ end
77
+ o.on('-U', '--unified [LINES]', Numeric,
78
+ 'Displays a unified diff with LINES lines',
79
+ 'of context. Default 3 lines.') do |ctx|
80
+ @format = :unified
81
+ @lines = ctx || 3
82
+ end
83
+ o.on('-e',
84
+ 'Creates an \'ed\' script to change',
85
+ 'oldfile to newfile.') do |ctx|
86
+ @format = :ed
87
+ end
88
+ o.on('-f',
89
+ 'Creates an \'ed\' script to change',
90
+ 'oldfile to newfile in reverse order.') do |ctx|
91
+ @format = :reverse_ed
92
+ end
93
+ o.on('-a', '--text',
94
+ 'Treat the files as text and compare them',
95
+ 'line-by-line, even if they do not seem',
96
+ 'to be text.') do |txt|
97
+ @binary = false
98
+ end
99
+ o.on('--binary',
100
+ 'Treats the files as binary.') do |bin|
101
+ @binary = true
102
+ end
103
+ o.on('-q', '--brief',
104
+ 'Report only whether or not the files',
105
+ 'differ, not the details.') do |ctx|
106
+ @format = :report
107
+ end
108
+ o.on_tail('--help', 'Shows this text.') do
109
+ error << o
110
+ return 0
111
+ end
112
+ o.on_tail('--version', 'Shows the version of Diff::LCS.') do
113
+ error << BANNER
114
+ return 0
115
+ end
116
+ o.on_tail ""
117
+ o.on_tail 'By default, runs produces an "old-style" diff, with output like UNIX diff.'
118
+ o.parse!
119
+ end
120
+
121
+ unless args.size == 2
122
+ error << args.options
123
+ return 127
124
+ end
125
+
126
+ # Defaults are for old-style diff
127
+ @format ||= :old
128
+ @lines ||= 0
129
+
130
+ file_old, file_new = *ARGV
131
+
132
+ case @format
133
+ when :context
134
+ char_old = '*' * 3
135
+ char_new = '-' * 3
136
+ when :unified
137
+ char_old = '-' * 3
138
+ char_new = '+' * 3
139
+ end
140
+
141
+ # After we've read up to a certain point in each file, the number of
142
+ # items we've read from each file will differ by FLD (could be 0).
143
+ file_length_difference = 0
144
+
145
+ if @binary.nil? or @binary
146
+ data_old = IO::read(file_old)
147
+ data_new = IO::read(file_new)
148
+
149
+ # Test binary status
150
+ if @binary.nil?
151
+ old_txt = data_old[0...4096].grep(/\0/).empty?
152
+ new_txt = data_new[0...4096].grep(/\0/).empty?
153
+ @binary = (not old_txt) or (not new_txt)
154
+ old_txt = new_txt = nil
155
+ end
156
+
157
+ unless @binary
158
+ data_old = data_old.split(/\n/).map! { |e| e.chomp }
159
+ data_new = data_new.split(/\n/).map! { |e| e.chomp }
160
+ end
161
+ else
162
+ data_old = IO::readlines(file_old).map! { |e| e.chomp }
163
+ data_new = IO::readlines(file_new).map! { |e| e.chomp }
164
+ end
165
+
166
+ # diff yields lots of pieces, each of which is basically a Block object
167
+ if @binary
168
+ diffs = (data_old == data_new)
169
+ else
170
+ diffs = Diff::LCS.diff(data_old, data_new)
171
+ diffs = nil if diffs.empty?
172
+ end
173
+
174
+ return 0 unless diffs
175
+
176
+ if (@format == :report) and diffs
177
+ output << "Files #{file_old} and #{file_new} differ\n"
178
+ return 1
179
+ end
180
+
181
+ if (@format == :unified) or (@format == :context)
182
+ ft = File.stat(file_old).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S %z')
183
+ puts "#{char_old} #{file_old}\t#{ft}"
184
+ ft = File.stat(file_new).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S %z')
185
+ puts "#{char_new} #{file_new}\t#{ft}"
186
+ end
187
+
188
+ # Loop over hunks. If a hunk overlaps with the last hunk, join them.
189
+ # Otherwise, print out the old one.
190
+ oldhunk = hunk = nil
191
+
192
+ if @format == :ed
193
+ real_output = output
194
+ output = []
195
+ end
196
+
197
+ diffs.each do |piece|
198
+ begin
199
+ hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, @lines,
200
+ file_length_difference)
201
+ file_length_difference = hunk.file_length_difference
202
+
203
+ next unless oldhunk
204
+
205
+ if (@lines > 0) and hunk.overlaps?(oldhunk)
206
+ hunk.unshift(oldhunk)
207
+ else
208
+ output << oldhunk.diff(@format)
209
+ end
210
+ ensure
211
+ oldhunk = hunk
212
+ output << "\n"
213
+ end
214
+ end
215
+
216
+ output << oldhunk.diff(@format)
217
+ output << "\n"
218
+
219
+ if @format == :ed
220
+ output.reverse_each { |e| real_output << e.diff(:ed_finish) }
221
+ end
222
+
223
+ return 1
224
+ end
225
+ end
226
+ end