diff_to_html 0.0.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.
- 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: []
|