git-commit-mailer 1.0.0

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.
@@ -0,0 +1,563 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2009 Ryo Onodera <onodera@clear-code.com>
4
+ # Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ class GitCommitMailer
20
+ class HTMLMailBodyFormatter < MailBodyFormatter
21
+ include ERB::Util
22
+
23
+ def format
24
+ @indent_level = 0
25
+ super
26
+ end
27
+
28
+ private
29
+ def template
30
+ <<-EOT
31
+ <!DOCTYPE html>
32
+ <html>
33
+ <head>
34
+ </head>
35
+ <body>
36
+ <%= dl_start %>
37
+ <%= dt("Author") %>
38
+ <%= dd(h("\#{@info.author_name} <\#{@info.author_email}>")) %>
39
+ <%= dt("Date") %>
40
+ <%= dd(h(@mailer.format_time(@info.date))) %>
41
+ <%= dt("New Revision") %>
42
+ <%= dd(format_revision) %>
43
+ <% unless @info.merge_status.empty? %>
44
+ <%= dt("Merge") %>
45
+ <%= dd_start %>
46
+ <ul>
47
+ <% @info.merge_status.each do |status| %>
48
+ <li><%= h(status) %></li>
49
+ <% end %>
50
+ </ul>
51
+ </dd>
52
+ <% end %>
53
+ <%= dt("Message") %>
54
+ <%= dd(pre(h(@info.summary.strip))) %>
55
+ <%= format_files("Added", @info.added_files) %>
56
+ <%= format_files("Copied", @info.copied_files) %>
57
+ <%= format_files("Removed", @info.deleted_files) %>
58
+ <%= format_files("Modified", @info.updated_files) %>
59
+ <%= format_files("Renamed", @info.renamed_files) %>
60
+ <%= format_files("Type Changed", @info.type_changed_files) %>
61
+ </dl>
62
+
63
+ <%= format_diffs %>
64
+ </body>
65
+ </html>
66
+ EOT
67
+ end
68
+
69
+ def format_revision
70
+ revision = @info.revision
71
+ url = commit_url
72
+ if url
73
+ formatted_revision = "<a href=\"#{h(url)}\">#{h(revision)}</a>"
74
+ else
75
+ formatted_revision = h(revision)
76
+ end
77
+ formatted_revision
78
+ end
79
+
80
+ def format_files(title, items)
81
+ return "" if items.empty?
82
+
83
+ formatted_files = ""
84
+ formatted_files << " #{dt(h(title) + ' files')}\n"
85
+ formatted_files << " #{dd_start}\n"
86
+ formatted_files << " <ul>\n"
87
+ items.each do |item_name, new_item_name|
88
+ if new_item_name.nil?
89
+ formatted_files << " <li>#{format_file(item_name)}</li>\n"
90
+ else
91
+ formatted_files << " <li>\n"
92
+ formatted_files << " #{format_file(new_item_name)}<br>\n"
93
+ formatted_files << " (from #{item_name})\n"
94
+ formatted_files << " </li>\n"
95
+ end
96
+ end
97
+ formatted_files << " </ul>\n"
98
+ formatted_files << " </dd>\n"
99
+ formatted_files
100
+ end
101
+
102
+ def format_file(file)
103
+ content = h(file)
104
+ url = commit_file_url(file)
105
+ if url
106
+ content = tag("a", {"href" => url}, content)
107
+ end
108
+ content
109
+ end
110
+
111
+ def format_diffs
112
+ return "" if @info.diffs.empty?
113
+
114
+ formatted_diff = ""
115
+ formatted_diff << " #{div_diff_section_start}\n"
116
+ @indent_level = 3
117
+ @info.diffs.each do |diff|
118
+ formatted_diff << "#{format_diff(diff)}\n"
119
+ end
120
+ formatted_diff << " </div>\n"
121
+ formatted_diff
122
+ end
123
+
124
+ def format_diff(diff)
125
+ header_column = format_header_column(diff)
126
+ from_line_column, to_line_column, content_column =
127
+ format_body_columns(diff)
128
+
129
+ table_diff do
130
+ head = tag("thead") do
131
+ tr_diff_header do
132
+ tag("td", {"colspan" => "3"}) do
133
+ pre_column(header_column)
134
+ end
135
+ end
136
+ end
137
+
138
+ body = tag("tbody") do
139
+ tag("tr") do
140
+ [
141
+ th_diff_line_number {pre_column(from_line_column)},
142
+ th_diff_line_number {pre_column(to_line_column)},
143
+ td_diff_content {pre_column(content_column)},
144
+ ]
145
+ end
146
+ end
147
+
148
+ [head, body]
149
+ end
150
+ end
151
+
152
+ def format_header_column(diff)
153
+ header_column = ""
154
+ diff.format_header.each_line do |line|
155
+ line = line.chomp
156
+ case line
157
+ when /^=/
158
+ header_column << span_diff_header_mark(h(line))
159
+ else
160
+ header_column << span_diff_header(h(line))
161
+ end
162
+ header_column << "\n"
163
+ end
164
+ header_column
165
+ end
166
+
167
+ def format_body_columns(diff)
168
+ from_line_column = ""
169
+ to_line_column = ""
170
+ content_column = ""
171
+ file_path = diff.file_path
172
+ diff.changes.each do |type, line_number, line|
173
+ case type
174
+ when :hunk_header
175
+ from_line_number, to_line_number = line_number
176
+ from_line_column << span_line_number_hunk_header(file_path, :from,
177
+ from_line_number)
178
+ to_line_column << span_line_number_hunk_header(file_path, :to,
179
+ to_line_number)
180
+ case line
181
+ when /\A(@@[\s0-9\-+,]+@@\s*)(.+)(\s*)\z/
182
+ hunk_info = $1
183
+ context = $2
184
+ formatted_line = h(hunk_info) + span_diff_context(h(context))
185
+ else
186
+ formatted_line = h(line)
187
+ end
188
+ content_column << span_diff_hunk_header(formatted_line)
189
+ when :added
190
+ from_line_column << span_line_number_nothing
191
+ to_line_column << span_line_number_added(file_path, line_number)
192
+ content_column << span_diff_added(h(line))
193
+ when :deleted
194
+ from_line_column << span_line_number_deleted(file_path, line_number)
195
+ to_line_column << span_line_number_nothing
196
+ content_column << span_diff_deleted(h(line))
197
+ when :not_changed
198
+ from_line_number, to_line_number = line_number
199
+ from_line_column << span_line_number_not_changed(file_path, :from,
200
+ from_line_number)
201
+ to_line_column << span_line_number_not_changed(file_path, :to,
202
+ to_line_number)
203
+ content_column << span_diff_not_changed(h(line))
204
+ end
205
+ from_line_column << "\n"
206
+ to_line_column << "\n"
207
+ content_column << "\n"
208
+ end
209
+ [from_line_column, to_line_column, content_column]
210
+ end
211
+
212
+ def tag_start(name, attributes)
213
+ start_tag = "<#{name}"
214
+ unless attributes.empty?
215
+ sorted_attributes = attributes.sort_by do |key, value|
216
+ key
217
+ end
218
+ formatted_attributes = sorted_attributes.collect do |key, value|
219
+ if value.is_a?(Hash)
220
+ sorted_value = value.sort_by do |value_key, value_value|
221
+ value_key
222
+ end
223
+ value = sorted_value.collect do |value_key, value_value|
224
+ "#{value_key}: #{value_value}"
225
+ end
226
+ end
227
+ if value.is_a?(Array)
228
+ value = value.sort.join("; ")
229
+ end
230
+ "#{h(key)}=\"#{h(value)}\""
231
+ end
232
+ formatted_attributes = formatted_attributes.join(" ")
233
+ start_tag << " #{formatted_attributes}"
234
+ end
235
+ start_tag << ">"
236
+ start_tag
237
+ end
238
+
239
+ def tag(name, attributes={}, content=nil, &block)
240
+ block_used = false
241
+ if content.nil? and block_given?
242
+ @indent_level += 1
243
+ if block.arity == 1
244
+ content = []
245
+ yield(content)
246
+ else
247
+ content = yield
248
+ end
249
+ @indent_level -= 1
250
+ block_used = true
251
+ end
252
+ content ||= ""
253
+ if content.is_a?(Array)
254
+ if block_used
255
+ separator = "\n"
256
+ else
257
+ separator = ""
258
+ end
259
+ content = content.join(separator)
260
+ end
261
+
262
+ formatted_tag = ""
263
+ formatted_tag << " " * @indent_level if block_used
264
+ formatted_tag << tag_start(name, attributes)
265
+ formatted_tag << "\n" if block_used
266
+ formatted_tag << content
267
+ formatted_tag << "\n" + (" " * @indent_level) if block_used
268
+ formatted_tag << "</#{name}>"
269
+ formatted_tag
270
+ end
271
+
272
+ def dl_start
273
+ tag_start("dl",
274
+ "style" => {
275
+ "margin-left" => "2em",
276
+ "line-height" => "1.5",
277
+ })
278
+ end
279
+
280
+ def dt_margin
281
+ 8
282
+ end
283
+
284
+ def dt(content)
285
+ tag("dt",
286
+ {
287
+ "style" => {
288
+ "clear" => "both",
289
+ "float" => "left",
290
+ "width" => "#{dt_margin}em",
291
+ "font-weight" => "bold",
292
+ },
293
+ },
294
+ content)
295
+ end
296
+
297
+ def dd_start
298
+ tag_start("dd",
299
+ "style" => {
300
+ "margin-left" => "#{dt_margin + 0.5}em",
301
+ })
302
+ end
303
+
304
+ def dd(content)
305
+ "#{dd_start}#{content}</dd>"
306
+ end
307
+
308
+ def border_styles
309
+ {
310
+ "border" => "1px solid #aaa",
311
+ }
312
+ end
313
+
314
+ def pre(content, styles={})
315
+ font_families = [
316
+ "Consolas", "Menlo", "\"Liberation Mono\"",
317
+ "Courier", "monospace"
318
+ ]
319
+ pre_styles = {
320
+ "font-family" => font_families.join(", "),
321
+ "line-height" => "1.2",
322
+ "padding" => "0.5em",
323
+ "width" => "auto",
324
+ }
325
+ pre_styles = pre_styles.merge(border_styles)
326
+ tag("pre", {"style" => pre_styles.merge(styles)}, content)
327
+ end
328
+
329
+ def div_diff_section_start
330
+ tag_start("div",
331
+ "class" => "diff-section",
332
+ "style" => {
333
+ "clear" => "both",
334
+ })
335
+ end
336
+
337
+ def div_diff_start
338
+ tag_start("div",
339
+ "class" => "diff",
340
+ "style" => {
341
+ "margin-left" => "1em",
342
+ "margin-right" => "1em",
343
+ })
344
+ end
345
+
346
+ def table_diff(&block)
347
+ styles = {
348
+ "border-collapse" => "collapse",
349
+ }
350
+ tag("table",
351
+ {
352
+ "style" => border_styles.merge(styles),
353
+ },
354
+ &block)
355
+ end
356
+
357
+ def tr_diff_header(&block)
358
+ tag("tr",
359
+ {
360
+ "class" => "diff-header",
361
+ "style" => border_styles,
362
+ },
363
+ &block)
364
+ end
365
+
366
+ def th_diff_line_number(&block)
367
+ tag("th",
368
+ {
369
+ "class" => "diff-line-number",
370
+ "style" => border_styles,
371
+ },
372
+ &block)
373
+ end
374
+
375
+ def td_diff_content(&block)
376
+ tag("td",
377
+ {
378
+ "class" => "diff-content",
379
+ "style" => border_styles,
380
+ },
381
+ &block)
382
+ end
383
+
384
+ def pre_column(column)
385
+ pre(column,
386
+ "white-space" => "normal",
387
+ "margin" => "0",
388
+ "border" => "0")
389
+ end
390
+
391
+ def span_common_styles
392
+ {
393
+ "white-space" => "pre",
394
+ "display" => "block",
395
+ }
396
+ end
397
+
398
+ def span_context_styles
399
+ {
400
+ "background-color" => "#ffffaa",
401
+ "color" => "#000000",
402
+ }
403
+ end
404
+
405
+ def span_deleted_styles
406
+ {
407
+ "background-color" => "#ffaaaa",
408
+ "color" => "#000000",
409
+ }
410
+ end
411
+
412
+ def span_added_styles
413
+ {
414
+ "background-color" => "#aaffaa",
415
+ "color" => "#000000",
416
+ }
417
+ end
418
+
419
+ def span_line_number_styles
420
+ span_common_styles
421
+ end
422
+
423
+ def span_line_number_nothing
424
+ tag("span",
425
+ {
426
+ "class" => "diff-line-number-nothing",
427
+ "style" => span_line_number_styles,
428
+ },
429
+ "&nbsp;")
430
+ end
431
+
432
+ def span_line_number_hunk_header(file_path, direction, offset)
433
+ content = "..."
434
+ url = commit_file_line_number_url(file_path, direction, offset - 1)
435
+ if url
436
+ content = tag("a", {"href" => url}, content)
437
+ end
438
+ tag("span",
439
+ {
440
+ "class" => "diff-line-number-hunk-header",
441
+ "style" => span_line_number_styles,
442
+ },
443
+ content)
444
+ end
445
+
446
+ def span_line_number_deleted(file_path, line_number)
447
+ content = h(line_number.to_s)
448
+ url = commit_file_line_number_url(file_path, :from, line_number)
449
+ if url
450
+ content = tag("a", {"href" => url}, content)
451
+ end
452
+ tag("span",
453
+ {
454
+ "class" => "diff-line-number-deleted",
455
+ "style" => span_line_number_styles.merge(span_deleted_styles),
456
+ },
457
+ content)
458
+ end
459
+
460
+ def span_line_number_added(file_path, line_number)
461
+ content = h(line_number.to_s)
462
+ url = commit_file_line_number_url(file_path, :to, line_number)
463
+ if url
464
+ content = tag("a", {"href" => url}, content)
465
+ end
466
+ tag("span",
467
+ {
468
+ "class" => "diff-line-number-added",
469
+ "style" => span_line_number_styles.merge(span_added_styles),
470
+ },
471
+ content)
472
+ end
473
+
474
+ def span_line_number_not_changed(file_path, direction, line_number)
475
+ content = h(line_number.to_s)
476
+ url = commit_file_line_number_url(file_path, direction, line_number)
477
+ if url
478
+ content = tag("a", {"href" => url}, content)
479
+ end
480
+ tag("span",
481
+ {
482
+ "class" => "diff-line-number-not-changed",
483
+ "style" => span_line_number_styles,
484
+ },
485
+ content)
486
+ end
487
+
488
+ def span_diff_styles
489
+ span_common_styles
490
+ end
491
+
492
+ def span_diff_metadata_styles
493
+ styles = {
494
+ "background-color" => "#eaf2f5",
495
+ "color" => "#999999",
496
+ }
497
+ span_diff_styles.merge(styles)
498
+ end
499
+
500
+ def span_diff_header(content)
501
+ tag("span",
502
+ {
503
+ "class" => "diff-header",
504
+ "style" => span_diff_metadata_styles,
505
+ },
506
+ content)
507
+ end
508
+
509
+ def span_diff_header_mark(content)
510
+ tag("span",
511
+ {
512
+ "class" => "diff-header-mark",
513
+ "style" => span_diff_metadata_styles,
514
+ },
515
+ content)
516
+ end
517
+
518
+ def span_diff_hunk_header(content)
519
+ tag("span",
520
+ {
521
+ "class" => "diff-hunk-header",
522
+ "style" => span_diff_metadata_styles,
523
+ },
524
+ content)
525
+ end
526
+
527
+ def span_diff_context(content)
528
+ tag("span",
529
+ {
530
+ "class" => "diff-context",
531
+ "style" => span_context_styles,
532
+ },
533
+ content)
534
+ end
535
+
536
+ def span_diff_deleted(content)
537
+ tag("span",
538
+ {
539
+ "class" => "diff-deleted",
540
+ "style" => span_diff_styles.merge(span_deleted_styles),
541
+ },
542
+ content)
543
+ end
544
+
545
+ def span_diff_added(content)
546
+ tag("span",
547
+ {
548
+ "class" => "diff-added",
549
+ "style" => span_diff_styles.merge(span_added_styles),
550
+ },
551
+ content)
552
+ end
553
+
554
+ def span_diff_not_changed(content)
555
+ tag("span",
556
+ {
557
+ "class" => "diff-not-changed",
558
+ "style" => span_diff_styles,
559
+ },
560
+ content)
561
+ end
562
+ end
563
+ end