diff_to_html 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +40 -0
- data/lib/diff_to_html.rb +6 -0
- data/lib/diff_to_html/converter.rb +230 -0
- data/lib/diff_to_html/git_converter.rb +14 -0
- data/lib/diff_to_html/svn_converter.rb +14 -0
- data/lib/diff_to_html/version.rb +3 -0
- metadata +58 -0
data/README.rdoc
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
= diff_to_html
|
2
|
+
|
3
|
+
Ruby library to convert unified diffs like you get from SVN and git to HTML
|
4
|
+
|
5
|
+
It'a fork of https://github.com/artemv/diff_to_html.rb to use as a dependency in gem specs of other gems through Rubygems.org hosting.
|
6
|
+
|
7
|
+
It's furthermore based on code from http://gurge.com/blog/2006/10/03/subversion-diff-viewer-cgi-in-ruby (thanks Adam
|
8
|
+
Doppelt!), adopted lightly to support multifile diffs and to have more familiar output. It definitely have
|
9
|
+
things to improve, so contribution/patches are very welcome.
|
10
|
+
|
11
|
+
* install the gem:
|
12
|
+
|
13
|
+
gem install diff_to_html
|
14
|
+
|
15
|
+
* go to gem's 'examples' dir and run 'ruby test.rb >out.html' -
|
16
|
+
this will get diff from sample 'svn diff' and generate out.html. Resulting html is linked to diff.css in 'examples' directory - you'll need
|
17
|
+
to copy it to your project's dir to use it.
|
18
|
+
|
19
|
+
* To use in Rails project:
|
20
|
+
|
21
|
+
require 'diff_to_html'
|
22
|
+
diff = `cat #{File.join(File.dirname(__FILE__), 'diff.git')}`
|
23
|
+
converter = DiffToHtml::GitConverter.new #there's also DiffToHtml::SvnConverter
|
24
|
+
puts converter.composite_to_html(diff)
|
25
|
+
|
26
|
+
* to use in any Ruby program:
|
27
|
+
|
28
|
+
require 'rubygems'
|
29
|
+
require 'diff_to_html'
|
30
|
+
...
|
31
|
+
|
32
|
+
== License
|
33
|
+
|
34
|
+
diff_to_html is released under the MIT license.
|
35
|
+
|
36
|
+
== Authors and credits
|
37
|
+
|
38
|
+
Authors:: Artem Vasiliev
|
39
|
+
Original code:: Adam Doppelt, http://gurge.com/blog/2006/10/03/subversion-diff-viewer-cgi-in-ruby
|
40
|
+
|
data/lib/diff_to_html.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module DiffToHtml
|
3
|
+
autoload :Converter, File.expand_path('diff_to_html/converter', File.dirname(__FILE__))
|
4
|
+
autoload :SvnConverter, File.expand_path('diff_to_html/svn_converter', File.dirname(__FILE__))
|
5
|
+
autoload :GitConverter, File.expand_path('diff_to_html/git_converter', File.dirname(__FILE__))
|
6
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module DiffToHtml
|
5
|
+
class Converter
|
6
|
+
attr_accessor :file_prefix
|
7
|
+
|
8
|
+
def composite_to_html(composite_diff)
|
9
|
+
diffs_to_html get_diffs(composite_diff)
|
10
|
+
end
|
11
|
+
|
12
|
+
def diffs_to_html(diffs)
|
13
|
+
result = '<ul class="diff">'
|
14
|
+
@filenum = 0
|
15
|
+
|
16
|
+
diffs.each do |file_map|
|
17
|
+
result << get_single_file_diff(file_map[:filename], file_map[:file])
|
18
|
+
@filenum += 1
|
19
|
+
end
|
20
|
+
|
21
|
+
result << '</ul>'
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_single_file_diff(file_name, diff_file)
|
26
|
+
result = ""
|
27
|
+
diff = diff_file.split("\n")
|
28
|
+
diff, line = shift_until_first_line(diff)
|
29
|
+
|
30
|
+
if line =~ /^---/
|
31
|
+
result << begin_file(file_name)
|
32
|
+
result << get_single_file_diff_body(diff)
|
33
|
+
result << "</li>"
|
34
|
+
else
|
35
|
+
#"<div class='error'>#{line}</div>"
|
36
|
+
result =%Q{<li><h2><a name="F#{@filenum}" href="#F#{@filenum}">#{file_name}</a></h2>#{line}</li>}
|
37
|
+
end
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_single_file_diff_body(diff)
|
43
|
+
@last_op, @left, @right = ' ', [], []
|
44
|
+
|
45
|
+
if diff.is_a? String
|
46
|
+
diff = diff.split("\n")
|
47
|
+
diff, line = shift_until_first_line(diff)
|
48
|
+
end
|
49
|
+
|
50
|
+
diff.shift #+++
|
51
|
+
|
52
|
+
result = %Q{
|
53
|
+
<table class='diff'>
|
54
|
+
<colgroup>
|
55
|
+
<col class="lineno"/>
|
56
|
+
<col class="lineno"/>
|
57
|
+
<col class="content"/>
|
58
|
+
</colgroup>
|
59
|
+
}
|
60
|
+
|
61
|
+
range = diff.shift
|
62
|
+
left_ln, right_ln = range_info(range)
|
63
|
+
result << range_row(range)
|
64
|
+
|
65
|
+
diff.each do |line|
|
66
|
+
op = line[0,1]
|
67
|
+
line = line[1..-1] || ''
|
68
|
+
|
69
|
+
if op == '\\'
|
70
|
+
line = op + line
|
71
|
+
op = ' '
|
72
|
+
end
|
73
|
+
|
74
|
+
if ((@last_op != ' ' and op == ' ') or (@last_op == ' ' and op != ' '))
|
75
|
+
left_ln, right_ln = flush_changes(result, left_ln, right_ln)
|
76
|
+
end
|
77
|
+
|
78
|
+
# truncate and escape
|
79
|
+
line = CGI.escapeHTML(line)
|
80
|
+
|
81
|
+
case op
|
82
|
+
when ' '
|
83
|
+
@left.push(line)
|
84
|
+
@right.push(line)
|
85
|
+
when '-' then @left.push(line)
|
86
|
+
when '+' then @right.push(line)
|
87
|
+
when '@'
|
88
|
+
range = '@' + line
|
89
|
+
flush_changes(result, left_ln, right_ln)
|
90
|
+
left_ln, right_ln = range_info(range)
|
91
|
+
result << range_row(range)
|
92
|
+
else
|
93
|
+
flush_changes(result, left_ln, right_ln)
|
94
|
+
result << "</table></li>"
|
95
|
+
break
|
96
|
+
end
|
97
|
+
@last_op = op
|
98
|
+
end
|
99
|
+
|
100
|
+
flush_changes(result, left_ln, right_ln)
|
101
|
+
result << "</table>"
|
102
|
+
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
def file_header_pattern
|
107
|
+
raise "Method to be implemented in VCS-specific class"
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def get_diffs(composite_diff)
|
113
|
+
pattern = file_header_pattern
|
114
|
+
files = composite_diff.split(pattern)
|
115
|
+
headers = composite_diff.scan(pattern) #huh can't find a way to get both at once
|
116
|
+
files.shift if files[0] == '' #first one is junk usually
|
117
|
+
result = []
|
118
|
+
i = 0
|
119
|
+
|
120
|
+
files.each do |file|
|
121
|
+
result << {:filename => "#{file_prefix}#{get_filename(headers[i])}", :file => file}
|
122
|
+
i += 1
|
123
|
+
end
|
124
|
+
|
125
|
+
result
|
126
|
+
end
|
127
|
+
|
128
|
+
def shift_until_first_line(diff)
|
129
|
+
diff.shift if diff.first.match(/#index/)
|
130
|
+
|
131
|
+
line = nil
|
132
|
+
|
133
|
+
while line !~ /^---/ && !diff.empty?
|
134
|
+
line = diff.shift
|
135
|
+
end
|
136
|
+
|
137
|
+
[diff, line]
|
138
|
+
end
|
139
|
+
|
140
|
+
def begin_file(file)
|
141
|
+
result = %Q{
|
142
|
+
<li>
|
143
|
+
<h2><a name="F#{@filenum}" href="#F#{@filenum}">#{file}</a></h2>
|
144
|
+
}
|
145
|
+
|
146
|
+
result
|
147
|
+
end
|
148
|
+
|
149
|
+
def range_info(range)
|
150
|
+
left_ln, right_ln = range.gsub(/(@|-|\+)+/, '').strip.split(' ').map{|ln| ln.split(',')[0]}
|
151
|
+
|
152
|
+
begin
|
153
|
+
return Integer(left_ln), Integer(right_ln)
|
154
|
+
rescue Exception => e
|
155
|
+
raise NotImplementedError.new(
|
156
|
+
e.class.name + " (#{e.message}): " + range.inspect + " => [#{left_ln.inspect}, #{right_ln.inspect}]"
|
157
|
+
)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def range_row(range)
|
162
|
+
"<tr class='range'><td>...</td><td>...</td><td>#{range}</td></tr>"
|
163
|
+
end
|
164
|
+
|
165
|
+
def flush_changes(result, left_ln, right_ln)
|
166
|
+
x, left_ln, right_ln = get_diff_row(left_ln, right_ln)
|
167
|
+
result << x
|
168
|
+
@left.clear
|
169
|
+
@right.clear
|
170
|
+
return left_ln, right_ln
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# helper for building the next row in the diff
|
175
|
+
#
|
176
|
+
def get_diff_row(left_ln, right_ln)
|
177
|
+
result = []
|
178
|
+
if @left.length > 0 or @right.length > 0
|
179
|
+
modified = (@last_op != ' ')
|
180
|
+
|
181
|
+
if modified
|
182
|
+
left_class = " class='r'"
|
183
|
+
right_class = " class='a'"
|
184
|
+
result << "<tbody class='mod'>"
|
185
|
+
else
|
186
|
+
left_class = right_class = ''
|
187
|
+
end
|
188
|
+
|
189
|
+
result << @left.map do |line|
|
190
|
+
x = "<tr#{left_class}>#{ln_cell(left_ln, 'l')}"
|
191
|
+
|
192
|
+
if modified
|
193
|
+
x += ln_cell(nil)
|
194
|
+
else
|
195
|
+
x += ln_cell(right_ln, 'r')
|
196
|
+
right_ln += 1
|
197
|
+
end
|
198
|
+
|
199
|
+
x += "<td>#{line}</td></tr>"
|
200
|
+
|
201
|
+
left_ln += 1
|
202
|
+
|
203
|
+
x
|
204
|
+
end
|
205
|
+
|
206
|
+
if modified
|
207
|
+
result << @right.map do |line|
|
208
|
+
x = "<tr#{right_class}>#{ln_cell(nil)}#{ln_cell(right_ln, 'r')}<td>#{line}</td></tr>"
|
209
|
+
right_ln += 1
|
210
|
+
x
|
211
|
+
end
|
212
|
+
|
213
|
+
result << "</tbody>"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
return result.join("\n"), left_ln, right_ln
|
218
|
+
end
|
219
|
+
|
220
|
+
def ln_cell(ln, side = nil)
|
221
|
+
anchor = "f#{@filenum}#{side}#{ln}"
|
222
|
+
result = "<td class = 'ln'>"
|
223
|
+
result += "<a name='#{anchor}' href='##{anchor}'>" if ln
|
224
|
+
result += "#{ln}"
|
225
|
+
result += "</a>" if ln
|
226
|
+
result += "</td>"
|
227
|
+
result
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module DiffToHtml
|
3
|
+
class GitConverter < Converter
|
4
|
+
def file_header_pattern
|
5
|
+
/^diff --git.+/
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_filename(file_diff)
|
9
|
+
match = (file_diff =~ / b\/(.+)/)
|
10
|
+
raise "not matched!" if !match
|
11
|
+
$1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module DiffToHtml
|
3
|
+
class SvnConverter < Converter
|
4
|
+
def file_header_pattern
|
5
|
+
/^Index: .+/
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_filename(header)
|
9
|
+
match = (header =~ /^Index: (.+)/) #if we use this pattern file_header_pattern files split doesn't work
|
10
|
+
raise "header '#{header}' not matched!" if !match
|
11
|
+
$1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: diff_to_html
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Doppelt
|
9
|
+
- Artem Vasiliev
|
10
|
+
- Mathias Gawlista
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2013-05-13 00:00:00.000000000 Z
|
15
|
+
dependencies: []
|
16
|
+
description: Generates HTML view of given unified diff
|
17
|
+
email: gawlista@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files:
|
21
|
+
- README.rdoc
|
22
|
+
files:
|
23
|
+
- README.rdoc
|
24
|
+
- lib/diff_to_html/converter.rb
|
25
|
+
- lib/diff_to_html/git_converter.rb
|
26
|
+
- lib/diff_to_html/svn_converter.rb
|
27
|
+
- lib/diff_to_html/version.rb
|
28
|
+
- lib/diff_to_html.rb
|
29
|
+
homepage: http://github.com/applicat/diff_to_html
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options:
|
34
|
+
- --main
|
35
|
+
- README.rdoc
|
36
|
+
- --inline-source
|
37
|
+
- --charset=UTF-8
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.8.24
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: Unified diff to HTML converter
|
58
|
+
test_files: []
|