To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / lib / redmine / unified_diff.rb @ 441:cbce1fd3b1b7

History | View | Annotate | Download (7.15 KB)

1 441:cbce1fd3b1b7 Chris
# Redmine - project management software
2
# Copyright (C) 2006-2011  Jean-Philippe Lang
3 0:513646585e45 Chris
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
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.
17
18
module Redmine
19
  # Class used to parse unified diffs
20 441:cbce1fd3b1b7 Chris
  class UnifiedDiff < Array
21
    attr_reader :diff_type
22
23 0:513646585e45 Chris
    def initialize(diff, options={})
24
      options.assert_valid_keys(:type, :max_lines)
25
      diff = diff.split("\n") if diff.is_a?(String)
26 441:cbce1fd3b1b7 Chris
      @diff_type = options[:type] || 'inline'
27 0:513646585e45 Chris
      lines = 0
28
      @truncated = false
29 441:cbce1fd3b1b7 Chris
      diff_table = DiffTable.new(@diff_type)
30 0:513646585e45 Chris
      diff.each do |line|
31 245:051f544170fe Chris
        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
39 0:513646585e45 Chris
        unless diff_table.add_line line
40 245:051f544170fe Chris
          line.force_encoding(line_encoding) if line_encoding
41
          self << diff_table if diff_table.length > 0
42 0:513646585e45 Chris
          diff_table = DiffTable.new(diff_type)
43
        end
44
        lines += 1
45
        if options[:max_lines] && lines > options[:max_lines]
46
          @truncated = true
47
          break
48
        end
49
      end
50
      self << diff_table unless diff_table.empty?
51
      self
52
    end
53 245:051f544170fe Chris
54 0:513646585e45 Chris
    def truncated?; @truncated; end
55
  end
56
57
  # Class that represents a file diff
58 441:cbce1fd3b1b7 Chris
  class DiffTable < Array
59
    attr_reader :file_name
60 0:513646585e45 Chris
61
    # Initialize with a Diff file and the type of Diff View
62
    # The type view must be inline or sbs (side_by_side)
63
    def initialize(type="inline")
64
      @parsing = false
65 441:cbce1fd3b1b7 Chris
      @added = 0
66
      @removed = 0
67 0:513646585e45 Chris
      @type = type
68
    end
69
70
    # Function for add a line of this Diff
71
    # Returns false when the diff ends
72
    def add_line(line)
73
      unless @parsing
74
        if line =~ /^(---|\+\+\+) (.*)$/
75
          @file_name = $2
76
        elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
77
          @line_num_l = $2.to_i
78
          @line_num_r = $5.to_i
79
          @parsing = true
80
        end
81
      else
82
        if line =~ /^[^\+\-\s@\\]/
83
          @parsing = false
84
          return false
85
        elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
86
          @line_num_l = $2.to_i
87
          @line_num_r = $5.to_i
88
        else
89 441:cbce1fd3b1b7 Chris
          parse_line(line, @type)
90 0:513646585e45 Chris
        end
91
      end
92
      return true
93
    end
94 441:cbce1fd3b1b7 Chris
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
103
    end
104 0:513646585e45 Chris
105
    def inspect
106
      puts '### DIFF TABLE ###'
107
      puts "file : #{file_name}"
108
      self.each do |d|
109
        d.inspect
110
      end
111
    end
112
113 441:cbce1fd3b1b7 Chris
    private
114 0:513646585e45 Chris
115
    # Escape the HTML for the diff
116
    def escapeHTML(line)
117
        CGI.escapeHTML(line)
118
    end
119 441:cbce1fd3b1b7 Chris
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
129 0:513646585e45 Chris
130
    def parse_line(line, type="inline")
131
      if line[0, 1] == "+"
132 441:cbce1fd3b1b7 Chris
        diff = diff_for_added_line
133 0:513646585e45 Chris
        diff.line_right = escapeHTML line[1..-1]
134
        diff.nb_line_right = @line_num_r
135
        diff.type_diff_right = 'diff_in'
136
        @line_num_r += 1
137 441:cbce1fd3b1b7 Chris
        @added += 1
138 0:513646585e45 Chris
        true
139
      elsif line[0, 1] == "-"
140 441:cbce1fd3b1b7 Chris
        diff = Diff.new
141 0:513646585e45 Chris
        diff.line_left = escapeHTML line[1..-1]
142
        diff.nb_line_left = @line_num_l
143
        diff.type_diff_left = 'diff_out'
144 441:cbce1fd3b1b7 Chris
        self << diff
145 0:513646585e45 Chris
        @line_num_l += 1
146 441:cbce1fd3b1b7 Chris
        @removed += 1
147 0:513646585e45 Chris
        true
148 441:cbce1fd3b1b7 Chris
      else
149
        write_offsets
150
        if line[0, 1] =~ /\s/
151
          diff = Diff.new
152
          diff.line_right = escapeHTML line[1..-1]
153
          diff.nb_line_right = @line_num_r
154
          diff.line_left = escapeHTML line[1..-1]
155
          diff.nb_line_left = @line_num_l
156
          self << diff
157
          @line_num_l += 1
158
          @line_num_r += 1
159
          true
160
        elsif line[0, 1] = "\\"
161 0:513646585e45 Chris
          true
162
        else
163
          false
164
        end
165
      end
166
    end
167 441:cbce1fd3b1b7 Chris
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
198 0:513646585e45 Chris
199
  # A line of diff
200
  class Diff
201
    attr_accessor :nb_line_left
202
    attr_accessor :line_left
203
    attr_accessor :nb_line_right
204
    attr_accessor :line_right
205
    attr_accessor :type_diff_right
206
    attr_accessor :type_diff_left
207 441:cbce1fd3b1b7 Chris
    attr_accessor :offsets
208 0:513646585e45 Chris
209
    def initialize()
210
      self.nb_line_left = ''
211
      self.nb_line_right = ''
212
      self.line_left = ''
213
      self.line_right = ''
214
      self.type_diff_right = ''
215
      self.type_diff_left = ''
216
    end
217 441:cbce1fd3b1b7 Chris
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
249 0:513646585e45 Chris
250
    def inspect
251
      puts '### Start Line Diff ###'
252
      puts self.nb_line_left
253
      puts self.line_left
254
      puts self.nb_line_right
255
      puts self.line_right
256
    end
257
  end
258
end