artemv-diff_to_html 1.0.2 → 1.0.3
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 +9 -12
- data/examples/diff.css +74 -67
- data/examples/diff.git +3721 -0
- data/examples/diff.svn +175 -0
- data/examples/test.rb +94 -18
- data/examples/test_git_diff.rb +5 -0
- data/examples/test_svn_diff.rb +8 -0
- data/lib/diff_to_html.rb +100 -53
- metadata +6 -2
data/examples/diff.svn
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
Index: app/models/issue.rb
|
2
|
+
===================================================================
|
3
|
+
--- app/models/issue.rb (revision 1693)
|
4
|
+
+++ app/models/issue.rb (working copy)
|
5
|
+
@@ -254,4 +254,9 @@
|
6
|
+
def to_s
|
7
|
+
"#{tracker} ##{id}: #{subject}"
|
8
|
+
end
|
9
|
+
+
|
10
|
+
+ def active_versions
|
11
|
+
+ project.active_versions(:for => self)
|
12
|
+
+ end
|
13
|
+
+
|
14
|
+
end
|
15
|
+
Index: app/models/project.rb
|
16
|
+
===================================================================
|
17
|
+
--- app/models/project.rb (revision 1693)
|
18
|
+
+++ app/models/project.rb (working copy)
|
19
|
+
@@ -239,6 +239,14 @@
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
+ def active_versions(options = {})
|
24
|
+
+ issue = options[:for] if options[:for].is_a?(Issue)
|
25
|
+
+ versions.select do |v|
|
26
|
+
+ !v.completed? || issue && issue.fixed_version &&
|
27
|
+
+ v.id == issue.fixed_version.id
|
28
|
+
+ end
|
29
|
+
+ end
|
30
|
+
+
|
31
|
+
protected
|
32
|
+
def validate
|
33
|
+
errors.add(parent_id, " must be a root project") if parent and parent.parent
|
34
|
+
Index: app/views/issues/_form.rhtml
|
35
|
+
===================================================================
|
36
|
+
--- app/views/issues/_form.rhtml (revision 1693)
|
37
|
+
+++ app/views/issues/_form.rhtml (working copy)
|
38
|
+
@@ -30,7 +30,7 @@
|
39
|
+
{:controller => 'projects', :action => 'add_issue_category', :id => @project},
|
40
|
+
:class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
|
41
|
+
<%= content_tag('p', f.select(:fixed_version_id,
|
42
|
+
- (@project.versions.sort.collect {|v| [v.name, v.id]}),
|
43
|
+
+ (@issue.active_versions.sort.collect {|v| [v.name, v.id]}),
|
44
|
+
{ :include_blank => true })) unless @project.versions.empty? %>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
Index: app/views/issues/_form_update.rhtml
|
48
|
+
===================================================================
|
49
|
+
--- app/views/issues/_form_update.rhtml (revision 1693)
|
50
|
+
+++ app/views/issues/_form_update.rhtml (working copy)
|
51
|
+
@@ -5,6 +5,6 @@
|
52
|
+
<div class="splitcontentright">
|
53
|
+
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
|
54
|
+
<%= content_tag('p', f.select(:fixed_version_id,
|
55
|
+
- (@project.versions.sort.collect {|v| [v.name, v.id]}),
|
56
|
+
+ (@issue.active_versions.sort.collect {|v| [v.name, v.id]}),
|
57
|
+
{ :include_blank => true })) unless @project.versions.empty? %>
|
58
|
+
</div>
|
59
|
+
Index: app/views/issues/bulk_edit.rhtml
|
60
|
+
===================================================================
|
61
|
+
--- app/views/issues/bulk_edit.rhtml (revision 1693)
|
62
|
+
+++ app/views/issues/bulk_edit.rhtml (working copy)
|
63
|
+
@@ -27,7 +27,7 @@
|
64
|
+
<label><%= l(:field_fixed_version) %>:
|
65
|
+
<%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
|
66
|
+
content_tag('option', l(:label_none), :value => 'none') +
|
67
|
+
- options_from_collection_for_select(@project.versions, :id, :name)) %></label>
|
68
|
+
+ options_from_collection_for_select(@project.active_versions, :id, :name)) %></label>
|
69
|
+
</p>
|
70
|
+
|
71
|
+
<p>
|
72
|
+
Index: app/views/issues/context_menu.rhtml
|
73
|
+
===================================================================
|
74
|
+
--- app/views/issues/context_menu.rhtml (revision 1693)
|
75
|
+
+++ app/views/issues/context_menu.rhtml (working copy)
|
76
|
+
@@ -20,11 +20,11 @@
|
77
|
+
<% end -%>
|
78
|
+
</ul>
|
79
|
+
</li>
|
80
|
+
- <% unless @project.versions.empty? -%>
|
81
|
+
+ <% unless @issue.active_versions.empty? -%>
|
82
|
+
<li class="folder">
|
83
|
+
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
|
84
|
+
<ul>
|
85
|
+
- <% @project.versions.sort.each do |v| -%>
|
86
|
+
+ <% @issue.active_versions.sort.each do |v| -%>
|
87
|
+
<li><%= context_menu_link v.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[fixed_version_id]' => v, :back_to => @back}, :method => :post,
|
88
|
+
:selected => (v == @issue.fixed_version), :disabled => !@can[:update] %></li>
|
89
|
+
<% end -%>
|
90
|
+
Index: test/fixtures/issues.yml
|
91
|
+
===================================================================
|
92
|
+
--- test/fixtures/issues.yml (revision 1693)
|
93
|
+
+++ test/fixtures/issues.yml (working copy)
|
94
|
+
@@ -91,4 +91,19 @@
|
95
|
+
status_id: 1
|
96
|
+
start_date: <%= Date.today.to_s(:db) %>
|
97
|
+
due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
|
98
|
+
-
|
99
|
+
|
100
|
+
+issues_007:
|
101
|
+
+ created_on: 2006-07-19 21:04:21 +02:00
|
102
|
+
+ project_id: 1
|
103
|
+
+ updated_on: 2006-07-19 21:09:50 +02:00
|
104
|
+
+ priority_id: 5
|
105
|
+
+ subject: Some closed issue
|
106
|
+
+ id: 7
|
107
|
+
+ fixed_version_id: 1
|
108
|
+
+ category_id:
|
109
|
+
+ description: Some closed issue
|
110
|
+
+ tracker_id: 2
|
111
|
+
+ assigned_to_id: 3
|
112
|
+
+ author_id: 2
|
113
|
+
+ status_id: 5
|
114
|
+
+ start_date: <%= 2.day.ago.to_date.to_s(:db) %>
|
115
|
+
+ due_date:
|
116
|
+
Index: test/unit/issue_test.rb
|
117
|
+
===================================================================
|
118
|
+
--- test/unit/issue_test.rb (revision 1693)
|
119
|
+
+++ test/unit/issue_test.rb (working copy)
|
120
|
+
@@ -181,4 +181,16 @@
|
121
|
+
assert_nil Issue.find_by_id(1)
|
122
|
+
assert_nil TimeEntry.find_by_issue_id(1)
|
123
|
+
end
|
124
|
+
+
|
125
|
+
+ def test_active_versions_have_current_even_if_completed
|
126
|
+
+ issue = issues(:issues_007)
|
127
|
+
+ assert issue.fixed_version.completed?
|
128
|
+
+ assert issue.active_versions.include?(issue.fixed_version),
|
129
|
+
+ 'Even if version is completed it should be considered active for issue assigned to it'
|
130
|
+
+ end
|
131
|
+
+
|
132
|
+
+ def test_active_versions_work_for_new_issue
|
133
|
+
+ issue = Issue.new(:project_id => projects(:projects_001).id)
|
134
|
+
+ assert !issue.active_versions.empty?, 'there should be active versions for new issue'
|
135
|
+
+ end
|
136
|
+
end
|
137
|
+
Index: test/unit/project_test.rb
|
138
|
+
===================================================================
|
139
|
+
--- test/unit/project_test.rb (revision 1693)
|
140
|
+
+++ test/unit/project_test.rb (working copy)
|
141
|
+
@@ -18,7 +18,7 @@
|
142
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
143
|
+
|
144
|
+
class ProjectTest < Test::Unit::TestCase
|
145
|
+
- fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, :users, :members, :roles, :projects_trackers, :trackers, :boards
|
146
|
+
+ fixtures :all
|
147
|
+
|
148
|
+
def setup
|
149
|
+
@ecookbook = Project.find(1)
|
150
|
+
@@ -130,4 +130,25 @@
|
151
|
+
assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
|
152
|
+
assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
|
153
|
+
end
|
154
|
+
+
|
155
|
+
+ def test_active_versions_have_future_versions
|
156
|
+
+ version = versions(:versions_003)
|
157
|
+
+ assert !version.completed?
|
158
|
+
+ assert projects(:projects_001).active_versions.include?(version),
|
159
|
+
+ 'Future version (or one without effective date) should be included'
|
160
|
+
+ end
|
161
|
+
+
|
162
|
+
+ def test_active_versions_have_incomplete_versions
|
163
|
+
+ version = versions(:versions_002)
|
164
|
+
+ assert !version.completed?
|
165
|
+
+ assert projects(:projects_001).active_versions.include?(version),
|
166
|
+
+ 'Incomplete versions should be included'
|
167
|
+
+ end
|
168
|
+
+
|
169
|
+
+ def test_active_versions_dont_have_completed
|
170
|
+
+ version = versions(:versions_001)
|
171
|
+
+ assert version.completed?
|
172
|
+
+ assert !projects(:projects_001).active_versions.include?(version),
|
173
|
+
+ 'Completed versions shouldn\'t be included'
|
174
|
+
+ end
|
175
|
+
end
|
data/examples/test.rb
CHANGED
@@ -1,18 +1,94 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
<
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
1
|
+
def out(diff, converter, title)
|
2
|
+
puts <<-EOF
|
3
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
4
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
6
|
+
<head>
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
8
|
+
<title>#{title}</title>
|
9
|
+
<style type="text/css">
|
10
|
+
<!--
|
11
|
+
body, p, ol, ul, td {
|
12
|
+
font-family:verdana,arial,helvetica,sans-serif;
|
13
|
+
font-size:13px;
|
14
|
+
}
|
15
|
+
|
16
|
+
a, a:link, a:visited {
|
17
|
+
color:#507EC0;
|
18
|
+
text-decoration:none;
|
19
|
+
}
|
20
|
+
|
21
|
+
ul.diff {
|
22
|
+
padding:0;
|
23
|
+
}
|
24
|
+
|
25
|
+
.diff table col.lineno {
|
26
|
+
width:4em;
|
27
|
+
}
|
28
|
+
|
29
|
+
.diff h2 {
|
30
|
+
color:#333333;
|
31
|
+
font-size:14px;
|
32
|
+
letter-spacing:normal;
|
33
|
+
margin:0pt auto;
|
34
|
+
padding:0.1em 0pt 0.25em 0.5em;
|
35
|
+
}
|
36
|
+
|
37
|
+
table.diff {
|
38
|
+
font-size : 9pt;
|
39
|
+
font-family : "lucida console", "courier new", monospace;
|
40
|
+
white-space : pre;
|
41
|
+
border : 1px solid #D7D7D7;
|
42
|
+
border-collapse : collapse;
|
43
|
+
line-height : 110%;
|
44
|
+
width: 100%;
|
45
|
+
}
|
46
|
+
|
47
|
+
.diff tr {
|
48
|
+
background: white;
|
49
|
+
}
|
50
|
+
|
51
|
+
.diff td {
|
52
|
+
border : none;
|
53
|
+
padding : 0px 10px;
|
54
|
+
margin : 0px;
|
55
|
+
}
|
56
|
+
|
57
|
+
.diff td a {
|
58
|
+
text-decoration: none;
|
59
|
+
}
|
60
|
+
|
61
|
+
tr.a { background : #ddffdd; }
|
62
|
+
|
63
|
+
tr.r { background : #ffdddd; }
|
64
|
+
|
65
|
+
tr.range { background : #EAF2F5; color : #999; }
|
66
|
+
|
67
|
+
td.ln {
|
68
|
+
background : #ECECEC;
|
69
|
+
color : #aaa;
|
70
|
+
border-top:1px solid #999988;
|
71
|
+
border-bottom:1px solid #999988;
|
72
|
+
border-right:1px solid #D7D7D7;
|
73
|
+
}
|
74
|
+
|
75
|
+
.diff li {
|
76
|
+
background:#F7F7F7 none repeat scroll 0%;
|
77
|
+
border:1px solid #D7D7D7;
|
78
|
+
list-style-type:none;
|
79
|
+
margin:0pt 0pt 2em;
|
80
|
+
padding:2px;
|
81
|
+
}
|
82
|
+
|
83
|
+
.ln a {
|
84
|
+
color: #aaa;
|
85
|
+
}
|
86
|
+
-->
|
87
|
+
</style>
|
88
|
+
</head>
|
89
|
+
<body>
|
90
|
+
#{converter.composite_to_html(diff)}
|
91
|
+
</body>
|
92
|
+
</html>
|
93
|
+
EOF
|
94
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
#require File.join(File.dirname(__FILE__), '../lib/diff_to_html.rb')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'diff_to_html'
|
4
|
+
require File.join(File.dirname(__FILE__), 'test.rb')
|
5
|
+
filename = ARGV[0]
|
6
|
+
diff = `cat #{filename}`
|
7
|
+
converter = SvnDiffToHtml.new
|
8
|
+
out(diff, converter, filename)
|
data/lib/diff_to_html.rb
CHANGED
@@ -3,10 +3,6 @@ require 'cgi'
|
|
3
3
|
class DiffToHtml
|
4
4
|
|
5
5
|
attr_accessor :file_prefix
|
6
|
-
|
7
|
-
#
|
8
|
-
# globals
|
9
|
-
#
|
10
6
|
|
11
7
|
def ln_cell(ln, side = nil)
|
12
8
|
anchor = "f#{@filenum}#{side}#{ln}"
|
@@ -17,10 +13,10 @@ class DiffToHtml
|
|
17
13
|
result += "</td>"
|
18
14
|
result
|
19
15
|
end
|
16
|
+
|
20
17
|
#
|
21
18
|
# helper for building the next row in the diff
|
22
19
|
#
|
23
|
-
|
24
20
|
def get_diff_row(left_ln, right_ln)
|
25
21
|
result = []
|
26
22
|
if @left.length > 0 or @right.length > 0
|
@@ -56,10 +52,6 @@ class DiffToHtml
|
|
56
52
|
return result.join("\n"), left_ln, right_ln
|
57
53
|
end
|
58
54
|
|
59
|
-
#
|
60
|
-
# build the diff
|
61
|
-
#
|
62
|
-
|
63
55
|
def range_row(range)
|
64
56
|
"<tr class='range'><td>...</td<td>...</td><td>#{range}</td></tr>"
|
65
57
|
end
|
@@ -72,16 +64,14 @@ class DiffToHtml
|
|
72
64
|
end
|
73
65
|
|
74
66
|
def begin_file(file)
|
75
|
-
@filenum ||=0
|
76
67
|
result = <<EOF
|
77
|
-
<li><h2><a name="F#{@filenum}" href="#F#{@filenum}">#{
|
68
|
+
<li><h2><a name="F#{@filenum}" href="#F#{@filenum}">#{file}</a></h2><table class='diff'>
|
78
69
|
<colgroup>
|
79
70
|
<col class="lineno"/>
|
80
71
|
<col class="lineno"/>
|
81
72
|
<col class="content"/>
|
82
73
|
</colgroup>
|
83
74
|
EOF
|
84
|
-
@filenum += 1
|
85
75
|
result
|
86
76
|
end
|
87
77
|
|
@@ -93,7 +83,7 @@ EOF
|
|
93
83
|
return left_ln, right_ln
|
94
84
|
end
|
95
85
|
|
96
|
-
def
|
86
|
+
def get_single_file_diff(file_name, diff_file)
|
97
87
|
@last_op = ' '
|
98
88
|
@left = []
|
99
89
|
@right = []
|
@@ -102,61 +92,118 @@ EOF
|
|
102
92
|
|
103
93
|
diff = diff_file.split("\n")
|
104
94
|
|
105
|
-
|
106
|
-
|
107
|
-
diff.
|
108
|
-
|
109
|
-
|
110
|
-
|
95
|
+
diff.shift #index
|
96
|
+
line = nil
|
97
|
+
while line !~ /^---/ && !diff.empty?
|
98
|
+
line = diff.shift
|
99
|
+
end
|
100
|
+
header_old = line
|
101
|
+
if line =~ /^---/
|
102
|
+
diff.shift #+++
|
111
103
|
|
112
|
-
result <<
|
104
|
+
result << begin_file(file_name)
|
113
105
|
range = diff.shift
|
114
106
|
left_ln, right_ln = range_info(range)
|
115
107
|
result << range_row(range)
|
116
108
|
|
117
|
-
skip = 0
|
118
109
|
diff.each do |line|
|
119
|
-
|
120
|
-
|
110
|
+
op = line[0,1]
|
111
|
+
line = line[1..-1] || ''
|
112
|
+
if op == '\\'
|
113
|
+
line = op + line
|
114
|
+
op = ' '
|
115
|
+
end
|
116
|
+
|
117
|
+
if ((@last_op != ' ' and op == ' ') or (@last_op == ' ' and op != ' '))
|
118
|
+
left_ln, right_ln = flush_changes(result, left_ln, right_ln)
|
119
|
+
end
|
120
|
+
|
121
|
+
# truncate and escape
|
122
|
+
line = CGI.escapeHTML(line)
|
123
|
+
|
124
|
+
case op
|
125
|
+
when ' '
|
126
|
+
@left.push(line)
|
127
|
+
@right.push(line)
|
128
|
+
when '-' then @left.push(line)
|
129
|
+
when '+' then @right.push(line)
|
130
|
+
when '@'
|
131
|
+
range = '@' + line
|
132
|
+
flush_changes(result, left_ln, right_ln)
|
133
|
+
left_ln, right_ln = range_info(range)
|
134
|
+
result << range_row(range)
|
121
135
|
else
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
if ((@last_op != ' ' and op == ' ') or (@last_op == ' ' and op != ' '))
|
126
|
-
left_ln, right_ln = flush_changes(result, left_ln, right_ln)
|
127
|
-
end
|
128
|
-
|
129
|
-
# truncate and escape
|
130
|
-
line = CGI.escapeHTML(line)
|
131
|
-
|
132
|
-
case op
|
133
|
-
when ' '
|
134
|
-
@left.push(line)
|
135
|
-
@right.push(line)
|
136
|
-
when '-' then @left.push(line)
|
137
|
-
when '+' then @right.push(line)
|
138
|
-
when '@'
|
139
|
-
range = '@' + line
|
140
|
-
flush_changes(result, left_ln, right_ln)
|
141
|
-
left_ln, right_ln = range_info(range)
|
142
|
-
result << range_row(range)
|
143
|
-
when 'I'
|
144
|
-
file = line[6..-1]
|
145
|
-
flush_changes(result, left_ln, right_ln)
|
146
|
-
result << "</table></li>#{begin_file(file)}"
|
147
|
-
skip = 3
|
148
|
-
end
|
149
|
-
@last_op = op
|
136
|
+
flush_changes(result, left_ln, right_ln)
|
137
|
+
result << "</table></li>"
|
138
|
+
break
|
150
139
|
end
|
140
|
+
@last_op = op
|
151
141
|
end
|
152
142
|
|
153
143
|
flush_changes(result, left_ln, right_ln)
|
154
|
-
result << "</table></li
|
144
|
+
result << "</table></li>"
|
155
145
|
else
|
156
|
-
|
146
|
+
#"<div class='error'>#{header_old}</div>"
|
147
|
+
result =%Q{<li><h2><a name="F#{@filenum}" href="#F#{@filenum}">#{file_name}</a></h2>#{header_old}</li>}
|
157
148
|
end
|
158
149
|
|
159
150
|
result
|
160
151
|
end
|
161
152
|
|
153
|
+
def file_header_pattern
|
154
|
+
raise "Method to be implemented in VCS-specific class"
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_diffs(composite_diff)
|
158
|
+
pattern = file_header_pattern
|
159
|
+
files = composite_diff.split(pattern)
|
160
|
+
headers = composite_diff.scan(pattern) #huh can't find a way to get both at once
|
161
|
+
files.shift if files[0] == '' #first one is junk usually
|
162
|
+
result = []
|
163
|
+
i = 0
|
164
|
+
files.each do |file|
|
165
|
+
result << {:filename => "#{file_prefix}#{get_filename(headers[i])}", :file => file}
|
166
|
+
i += 1
|
167
|
+
end
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
def diffs_to_html(diffs)
|
172
|
+
result = '<ul class="diff">'
|
173
|
+
@filenum = 0
|
174
|
+
diffs.each do |file_map|
|
175
|
+
result << get_single_file_diff(file_map[:filename], file_map[:file])
|
176
|
+
@filenum += 1
|
177
|
+
end
|
178
|
+
result << '</ul>'
|
179
|
+
result
|
180
|
+
end
|
181
|
+
|
182
|
+
def composite_to_html(composite_diff)
|
183
|
+
diffs_to_html get_diffs(composite_diff)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class GitDiffToHtml < DiffToHtml
|
188
|
+
def file_header_pattern
|
189
|
+
/^diff --git.+/
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_filename(file_diff)
|
193
|
+
match = (file_diff =~ / b\/(.+)/)
|
194
|
+
raise "not matched!" if !match
|
195
|
+
$1
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class SvnDiffToHtml < DiffToHtml
|
200
|
+
def file_header_pattern
|
201
|
+
/^Index: .+/
|
202
|
+
end
|
203
|
+
|
204
|
+
def get_filename(header)
|
205
|
+
match = (header =~ /^Index: (.+)/) #if we use this pattern file_header_pattern files split doesn't work
|
206
|
+
raise "header '#{header}' not matched!" if !match
|
207
|
+
$1
|
208
|
+
end
|
162
209
|
end
|