Mercurial > hg > soundsoftware-site
comparison lib/redmine/unified_diff.rb @ 511:107d36338b70 live
Merge from branch "cannam"
author | Chris Cannam |
---|---|
date | Thu, 14 Jul 2011 10:43:07 +0100 |
parents | cbce1fd3b1b7 |
children | cbb26bc654de |
comparison
equal
deleted
inserted
replaced
451:a9f6345cb43d | 511:107d36338b70 |
---|---|
1 # redMine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006-2008 Jean-Philippe Lang | 2 # Copyright (C) 2006-2011 Jean-Philippe Lang |
3 # | 3 # |
4 # This program is free software; you can redistribute it and/or | 4 # This program is free software; you can redistribute it and/or |
5 # modify it under the terms of the GNU General Public License | 5 # modify it under the terms of the GNU General Public License |
6 # as published by the Free Software Foundation; either version 2 | 6 # as published by the Free Software Foundation; either version 2 |
7 # of the License, or (at your option) any later version. | 7 # of the License, or (at your option) any later version. |
15 # along with this program; if not, write to the Free Software | 15 # along with this program; if not, write to the Free Software |
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 |
18 module Redmine | 18 module Redmine |
19 # Class used to parse unified diffs | 19 # Class used to parse unified diffs |
20 class UnifiedDiff < Array | 20 class UnifiedDiff < Array |
21 attr_reader :diff_type | |
22 | |
21 def initialize(diff, options={}) | 23 def initialize(diff, options={}) |
22 options.assert_valid_keys(:type, :max_lines) | 24 options.assert_valid_keys(:type, :max_lines) |
23 diff = diff.split("\n") if diff.is_a?(String) | 25 diff = diff.split("\n") if diff.is_a?(String) |
24 diff_type = options[:type] || 'inline' | 26 @diff_type = options[:type] || 'inline' |
25 | |
26 lines = 0 | 27 lines = 0 |
27 @truncated = false | 28 @truncated = false |
28 diff_table = DiffTable.new(diff_type) | 29 diff_table = DiffTable.new(@diff_type) |
29 diff.each do |line| | 30 diff.each do |line| |
31 line_encoding = nil | |
32 if line.respond_to?(:force_encoding) | |
33 line_encoding = line.encoding | |
34 # TODO: UTF-16 and Japanese CP932 which is imcompatible with ASCII | |
35 # In Japan, diffrence between file path encoding | |
36 # and file contents encoding is popular. | |
37 line.force_encoding('ASCII-8BIT') | |
38 end | |
30 unless diff_table.add_line line | 39 unless diff_table.add_line line |
31 self << diff_table if diff_table.length > 1 | 40 line.force_encoding(line_encoding) if line_encoding |
41 self << diff_table if diff_table.length > 0 | |
32 diff_table = DiffTable.new(diff_type) | 42 diff_table = DiffTable.new(diff_type) |
33 end | 43 end |
34 lines += 1 | 44 lines += 1 |
35 if options[:max_lines] && lines > options[:max_lines] | 45 if options[:max_lines] && lines > options[:max_lines] |
36 @truncated = true | 46 @truncated = true |
38 end | 48 end |
39 end | 49 end |
40 self << diff_table unless diff_table.empty? | 50 self << diff_table unless diff_table.empty? |
41 self | 51 self |
42 end | 52 end |
43 | 53 |
44 def truncated?; @truncated; end | 54 def truncated?; @truncated; end |
45 end | 55 end |
46 | 56 |
47 # Class that represents a file diff | 57 # Class that represents a file diff |
48 class DiffTable < Hash | 58 class DiffTable < Array |
49 attr_reader :file_name, :line_num_l, :line_num_r | 59 attr_reader :file_name |
50 | 60 |
51 # Initialize with a Diff file and the type of Diff View | 61 # Initialize with a Diff file and the type of Diff View |
52 # The type view must be inline or sbs (side_by_side) | 62 # The type view must be inline or sbs (side_by_side) |
53 def initialize(type="inline") | 63 def initialize(type="inline") |
54 @parsing = false | 64 @parsing = false |
55 @nb_line = 1 | 65 @added = 0 |
56 @start = false | 66 @removed = 0 |
57 @before = 'same' | |
58 @second = true | |
59 @type = type | 67 @type = type |
60 end | 68 end |
61 | 69 |
62 # Function for add a line of this Diff | 70 # Function for add a line of this Diff |
63 # Returns false when the diff ends | 71 # Returns false when the diff ends |
76 return false | 84 return false |
77 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ | 85 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ |
78 @line_num_l = $2.to_i | 86 @line_num_l = $2.to_i |
79 @line_num_r = $5.to_i | 87 @line_num_r = $5.to_i |
80 else | 88 else |
81 @nb_line += 1 if parse_line(line, @type) | 89 parse_line(line, @type) |
82 end | 90 end |
83 end | 91 end |
84 return true | 92 return true |
93 end | |
94 | |
95 def each_line | |
96 prev_line_left, prev_line_right = nil, nil | |
97 each do |line| | |
98 spacing = prev_line_left && prev_line_right && (line.nb_line_left != prev_line_left+1) && (line.nb_line_right != prev_line_right+1) | |
99 yield spacing, line | |
100 prev_line_left = line.nb_line_left.to_i if line.nb_line_left.to_i > 0 | |
101 prev_line_right = line.nb_line_right.to_i if line.nb_line_right.to_i > 0 | |
102 end | |
85 end | 103 end |
86 | 104 |
87 def inspect | 105 def inspect |
88 puts '### DIFF TABLE ###' | 106 puts '### DIFF TABLE ###' |
89 puts "file : #{file_name}" | 107 puts "file : #{file_name}" |
90 self.each do |d| | 108 self.each do |d| |
91 d.inspect | 109 d.inspect |
92 end | 110 end |
93 end | 111 end |
94 | 112 |
95 private | 113 private |
96 # Test if is a Side By Side type | |
97 def sbs?(type, func) | |
98 if @start and type == "sbs" | |
99 if @before == func and @second | |
100 tmp_nb_line = @nb_line | |
101 self[tmp_nb_line] = Diff.new | |
102 else | |
103 @second = false | |
104 tmp_nb_line = @start | |
105 @start += 1 | |
106 @nb_line -= 1 | |
107 end | |
108 else | |
109 tmp_nb_line = @nb_line | |
110 @start = @nb_line | |
111 self[tmp_nb_line] = Diff.new | |
112 @second = true | |
113 end | |
114 unless self[tmp_nb_line] | |
115 @nb_line += 1 | |
116 self[tmp_nb_line] = Diff.new | |
117 else | |
118 self[tmp_nb_line] | |
119 end | |
120 end | |
121 | 114 |
122 # Escape the HTML for the diff | 115 # Escape the HTML for the diff |
123 def escapeHTML(line) | 116 def escapeHTML(line) |
124 CGI.escapeHTML(line) | 117 CGI.escapeHTML(line) |
125 end | 118 end |
119 | |
120 def diff_for_added_line | |
121 if @type == 'sbs' && @removed > 0 && @added < @removed | |
122 self[-(@removed - @added)] | |
123 else | |
124 diff = Diff.new | |
125 self << diff | |
126 diff | |
127 end | |
128 end | |
126 | 129 |
127 def parse_line(line, type="inline") | 130 def parse_line(line, type="inline") |
128 if line[0, 1] == "+" | 131 if line[0, 1] == "+" |
129 diff = sbs? type, 'add' | 132 diff = diff_for_added_line |
130 @before = 'add' | |
131 diff.line_right = escapeHTML line[1..-1] | 133 diff.line_right = escapeHTML line[1..-1] |
132 diff.nb_line_right = @line_num_r | 134 diff.nb_line_right = @line_num_r |
133 diff.type_diff_right = 'diff_in' | 135 diff.type_diff_right = 'diff_in' |
134 @line_num_r += 1 | 136 @line_num_r += 1 |
137 @added += 1 | |
135 true | 138 true |
136 elsif line[0, 1] == "-" | 139 elsif line[0, 1] == "-" |
137 diff = sbs? type, 'remove' | 140 diff = Diff.new |
138 @before = 'remove' | |
139 diff.line_left = escapeHTML line[1..-1] | 141 diff.line_left = escapeHTML line[1..-1] |
140 diff.nb_line_left = @line_num_l | 142 diff.nb_line_left = @line_num_l |
141 diff.type_diff_left = 'diff_out' | 143 diff.type_diff_left = 'diff_out' |
144 self << diff | |
142 @line_num_l += 1 | 145 @line_num_l += 1 |
146 @removed += 1 | |
143 true | 147 true |
144 elsif line[0, 1] =~ /\s/ | 148 else |
145 @before = 'same' | 149 write_offsets |
146 @start = false | 150 if line[0, 1] =~ /\s/ |
147 diff = Diff.new | 151 diff = Diff.new |
148 diff.line_right = escapeHTML line[1..-1] | 152 diff.line_right = escapeHTML line[1..-1] |
149 diff.nb_line_right = @line_num_r | 153 diff.nb_line_right = @line_num_r |
150 diff.line_left = escapeHTML line[1..-1] | 154 diff.line_left = escapeHTML line[1..-1] |
151 diff.nb_line_left = @line_num_l | 155 diff.nb_line_left = @line_num_l |
152 self[@nb_line] = diff | 156 self << diff |
153 @line_num_l += 1 | 157 @line_num_l += 1 |
154 @line_num_r += 1 | 158 @line_num_r += 1 |
155 true | 159 true |
156 elsif line[0, 1] = "\\" | 160 elsif line[0, 1] = "\\" |
157 true | 161 true |
158 else | 162 else |
159 false | 163 false |
160 end | 164 end |
161 end | 165 end |
162 end | 166 end |
167 | |
168 def write_offsets | |
169 if @added > 0 && @added == @removed | |
170 @added.times do |i| | |
171 line = self[-(1 + i)] | |
172 removed = (@type == 'sbs') ? line : self[-(1 + @added + i)] | |
173 offsets = offsets(removed.line_left, line.line_right) | |
174 removed.offsets = line.offsets = offsets | |
175 end | |
176 end | |
177 @added = 0 | |
178 @removed = 0 | |
179 end | |
180 | |
181 def offsets(line_left, line_right) | |
182 if line_left.present? && line_right.present? && line_left != line_right | |
183 max = [line_left.size, line_right.size].min | |
184 starting = 0 | |
185 while starting < max && line_left[starting] == line_right[starting] | |
186 starting += 1 | |
187 end | |
188 ending = -1 | |
189 while ending >= -(max - starting) && line_left[ending] == line_right[ending] | |
190 ending -= 1 | |
191 end | |
192 unless starting == 0 && ending == -1 | |
193 [starting, ending] | |
194 end | |
195 end | |
196 end | |
197 end | |
163 | 198 |
164 # A line of diff | 199 # A line of diff |
165 class Diff | 200 class Diff |
166 attr_accessor :nb_line_left | 201 attr_accessor :nb_line_left |
167 attr_accessor :line_left | 202 attr_accessor :line_left |
168 attr_accessor :nb_line_right | 203 attr_accessor :nb_line_right |
169 attr_accessor :line_right | 204 attr_accessor :line_right |
170 attr_accessor :type_diff_right | 205 attr_accessor :type_diff_right |
171 attr_accessor :type_diff_left | 206 attr_accessor :type_diff_left |
207 attr_accessor :offsets | |
172 | 208 |
173 def initialize() | 209 def initialize() |
174 self.nb_line_left = '' | 210 self.nb_line_left = '' |
175 self.nb_line_right = '' | 211 self.nb_line_right = '' |
176 self.line_left = '' | 212 self.line_left = '' |
177 self.line_right = '' | 213 self.line_right = '' |
178 self.type_diff_right = '' | 214 self.type_diff_right = '' |
179 self.type_diff_left = '' | 215 self.type_diff_left = '' |
180 end | 216 end |
217 | |
218 def type_diff | |
219 type_diff_right == 'diff_in' ? type_diff_right : type_diff_left | |
220 end | |
221 | |
222 def line | |
223 type_diff_right == 'diff_in' ? line_right : line_left | |
224 end | |
225 | |
226 def html_line_left | |
227 if offsets | |
228 line_left.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') | |
229 else | |
230 line_left | |
231 end | |
232 end | |
233 | |
234 def html_line_right | |
235 if offsets | |
236 line_right.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') | |
237 else | |
238 line_right | |
239 end | |
240 end | |
241 | |
242 def html_line | |
243 if offsets | |
244 line.dup.insert(offsets.first, '<span>').insert(offsets.last, '</span>') | |
245 else | |
246 line | |
247 end | |
248 end | |
181 | 249 |
182 def inspect | 250 def inspect |
183 puts '### Start Line Diff ###' | 251 puts '### Start Line Diff ###' |
184 puts self.nb_line_left | 252 puts self.nb_line_left |
185 puts self.line_left | 253 puts self.line_left |