/[cvs]/jonen/ruby/CVSspam/cvsspam.rb
ViewVC logotype

Annotation of /jonen/ruby/CVSspam/cvsspam.rb

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.12 - (hide annotations)
Sun Jan 19 01:42:21 2003 UTC (21 years, 5 months ago) by jonen
Branch: MAIN
Changes since 1.11: +1 -1 lines
+ bugfix

1 jonen 1.1 #!/usr/bin/ruby
2    
3     # collect_diffs.rb expects to find this script in the same directory as it
4     #
5    
6 jonen 1.3
7    
8 jonen 1.1 # TODO: exemplify syntax for 'cvs admin -m' when log message is missing
9     # TODO: make max-line limit on diff output configurable
10     # TODO: put max size limit on whole email
11     # TODO: support non-html mail too (text/plain, multipart/alternative)
12    
13     $maxSubjectLength = 200
14     $maxLinesPerDiff = 1000
15     $charset = nil # nil implies 'don't specify a charset'
16    
17     def blah(text)
18     puts("cvsspam.rb: #{text}") if $debug
19     end
20    
21     def min(a, b)
22     a<b ? a : b
23     end
24    
25    
26     # the regexps given as keys must not use capturing subexpressions '(...)'
27     class MultiSub
28     def initialize(hash)
29     @mash = Array.new
30     expr = nil
31     hash.each do |key,val|
32     if expr == nil ; expr="(" else expr<<"|(" end
33     expr << key << ")"
34     @mash << val
35     end
36     @re = Regexp.new(expr)
37     end
38    
39     def gsub!(text)
40     text.gsub!(@re) { |match|
41     idx = -1
42     $~.to_a.each { |subexp|
43     break unless idx==-1 || subexp==nil
44     idx += 1
45     }
46     idx==-1 ? match : @mash[idx].call(match)
47     }
48     end
49     end
50    
51     class LogReader
52     def initialize(logIO)
53     @io = logIO
54     advance
55     end
56    
57     def currentLineCode ; @line[1,1] end
58    
59    
60     class ConstrainedIO
61     def initialize(reader)
62     @reader = reader
63     @linecode = reader.currentLineCode
64     end
65    
66     def each
67     return if @reader == nil
68     while true
69     yield @reader.currentLine
70     break unless @reader.advance && currentValid?
71     end
72     @reader = nil
73     end
74    
75     def gets
76     return nil if @reader == nil
77     line = @reader.currentLine
78     return nil if line==nil || !currentValid?
79     @reader.advance
80     return line
81     end
82    
83     def currentValid?
84     @linecode == @reader.currentLineCode
85     end
86     end
87    
88     def getLines
89     ConstrainedIO.new(self)
90     end
91    
92     def eof ; @line==nil end
93    
94     def advance
95     @line = @io.gets
96     return false if @line == nil
97     unless @line[0,1] == "#"
98     raise "#{$logfile}:#{@io.lineno} line did not begin with '#': #{@line}"
99     end
100     return true
101     end
102    
103     def currentLine
104     @line==nil ? nil : @line[3, @line.length-4]
105     end
106     end
107    
108    
109     def htmlEncode(text)
110     text.gsub(/./) do
111     case $&
112     when "&" then "&amp;"
113     when "<" then "&lt;"
114     when ">" then "&gt;"
115     when "\"" then "&quot;"
116     else $&
117     end
118     end
119     end
120    
121     # actually, allows '/' to appear
122     def urlEncode(text)
123     text.sub(/[^a-zA-Z0-9\-,.*_\/]/) do
124     "%#{sprintf('%2X', $&[0])}"
125     end
126     end
127    
128     # a top-level directory under the $CVSROOT
129     class Repository
130     @@repositories = Hash.new
131    
132     def initialize(name)
133     @name = name
134     @common_prefix = nil
135     end
136    
137     # calculate the path prefix shared by all files commited to this
138     # reposotory
139     def merge_common_prefix(path)
140     if @common_prefix == nil
141     @common_prefix = path.dup
142     else
143     path = path.dup
144     until @common_prefix == path
145     if @common_prefix.size>path.size
146     if @common_prefix.sub!(/(.*)\/.*$/, '\1').nil?
147     raise "unable to merge '#{path}' in to '#{@common_prefix}': prefix totally different"
148     end
149     else
150     if path.sub!(/(.*)\/.*$/, '\1').nil?
151     raise "unable to merge '#{path}' in to '#{@common_prefix}': prefix totally different"
152     end
153     end
154     end
155     end
156     end
157    
158     attr_reader :name, :common_prefix
159    
160     def Repository.get(name)
161     name =~ /^[^\/]+/
162     name = $&
163     rep = @@repositories[name]
164     if rep.nil?
165     rep = Repository.new(name)
166     @@repositories[name] = rep
167     end
168     rep
169     end
170    
171     def Repository.count
172     @@repositories.size
173     end
174    
175     def Repository.each
176     @@repositories.each_value do |rep|
177     yield rep
178     end
179     end
180    
181     def Repository.array
182     @@repositories.values
183     end
184    
185     def to_s
186     @name
187     end
188     end
189    
190     class FileEntry
191     def initialize(path)
192     @path = path
193     @lineAdditions = @lineRemovals = 0
194     @repository = Repository.get(path)
195     @repository.merge_common_prefix(basedir())
196     end
197    
198     attr_accessor :path, :type, :lineAdditions, :lineRemovals, :isBinary, :isEmpty, :tag, :fromVer, :toVer
199    
200     def file
201     @path =~ /.*\/(.*)/
202     $1
203     end
204    
205     def basedir
206     @path =~ /(.*)\/.*/
207     $1
208     end
209    
210     def repository
211     @repository
212     end
213    
214     def name_after_common_prefix
215     @path.slice(@repository.common_prefix.size+1,@path.size-@repository.common_prefix.size-1)
216     end
217    
218     def removal?
219     @type == "R"
220     end
221    
222     def addition?
223     @type == "A"
224     end
225    
226     def modification?
227     @type == "M"
228     end
229     end
230    
231    
232     class LineConsumer
233     def handleLines(lines, emailIO)
234     @emailIO = emailIO
235     @lineCount = 0
236     setup
237     lines.each do |line|
238     @lineCount += 1
239     consume(line)
240     end
241     teardown
242     end
243    
244     def setup
245     end
246    
247     def teardown
248     end
249    
250     def lineno
251     @lineCount
252     end
253    
254     def println(text)
255     @emailIO.puts(text)
256     end
257    
258     def print(text)
259     @emailIO.print(text)
260     end
261     end
262    
263    
264     mailSub = proc { |match| "<a href=\"mailto:#{match}\">#{match}</a>" }
265     urlSub = proc { |match| "<a href=\"#{match}\">#{match}</a>" }
266     bugSub = proc { |match|
267     match =~ /([0-9]+)/
268     "<a href=\"#{$bugzillaURL.sub(/%s/, $1)}\">#{match}</a>"
269     }
270     commentSubstitutions = {
271     '(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
272     '\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub}
273    
274    
275     class CommentHandler < LineConsumer
276     def setup
277     @haveBlank = false
278     @comment = ""
279     end
280    
281     def consume(line)
282     if line =~ /^\s*$/
283     @haveBlank = true
284     else
285     if @haveBlank
286     @comment += "\n"
287     @haveBlank = false
288     end
289     $mailSubject = line if $mailSubject == nil
290     @comment += line += "\n"
291     end
292     end
293    
294     def teardown
295     unless @comment == @lastComment
296     println("<pre class=\"comment\">")
297     encoded = htmlEncode(@comment)
298     $commentEncoder.gsub!(encoded)
299     println(encoded)
300     println("</pre>")
301     @lastComment = @comment
302     end
303     end
304     end
305    
306    
307     class TagHandler < LineConsumer
308     def consume(line)
309     # TODO: check there is only one line
310     @tag = line
311     $haveTag = true
312     end
313    
314     def getLastTag
315     tmp = @tag
316     @tag = nil
317     tmp
318     end
319     end
320    
321     class VersionHandler < LineConsumer
322     def consume(line)
323     # TODO: check there is only one line
324     $fromVer,$toVer = line.split(/,/)
325     end
326     end
327    
328    
329     class FileHandler < LineConsumer
330     def setTagHandler(handler)
331     @tagHandler = handler
332     end
333    
334     def setup
335     $fileHeaderHtml = ''
336     println("<hr noshade=\"nodhade\" size=\"1\" /><a name=\"file#{$fileEntries.size+1}\" /><div class=\"file\">")
337     end
338    
339     def consume(line)
340     $file = FileEntry.new(line)
341     $fileEntries << $file
342     $file.tag = getTag
343     handleFile($file)
344     end
345    
346     protected
347     def getTag
348     @tagHandler.getLastTag
349     end
350    
351     def println(text)
352     $fileHeaderHtml << text << "\n"
353     end
354    
355     def print(text)
356     $fileHeaderHtml << text
357     end
358     end
359    
360     # if using viewcvs, create a link to the named path's dir listing
361     def viewcvsPath(path)
362     if $viewcvsURL == nil
363     htmlEncode(path)
364     else
365     "<a href=\"#{$viewcvsURL}#{urlEncode(path)}\">#{htmlEncode(path)}</a>"
366     end
367     end
368    
369     def viewcvsFile(path, version)
370     if $viewcvsURL == nil
371     version
372     else
373     "<a href=\"#{$viewcvsURL}#{urlEncode(path)}?rev=#{version}&content-type=text/vnd.viewcvs-markup\">#{version}</a>"
374     end
375     end
376    
377    
378     # in need of refactoring...
379    
380     class AddedFileHandler < FileHandler
381     def handleFile(file)
382     file.type="A"
383     file.toVer=$toVer
384     print("<span class=\"pathname\" id=\"added\">")
385     print(viewcvsPath(file.basedir))
386     println("<br /></span>")
387     println("<div class=\"fileheader\" id=\"added\"><big><b>#{htmlEncode(file.file)}</b></big> <small id=\"info\">added at #{viewcvsFile(file.path,file.toVer)}</small></div>")
388     end
389     end
390    
391     class RemovedFileHandler < FileHandler
392     def handleFile(file)
393     file.type="R"
394     file.fromVer=$fromVer
395     print("<span class=\"pathname\" id=\"removed\">")
396     print(viewcvsPath(file.basedir))
397     println("<br /></span>")
398     println("<div class=\"fileheader\" id=\"removed\"><big><b>#{htmlEncode(file.file)}</b></big> <small id=\"info\">removed after #{viewcvsFile(file.path,file.fromVer)}</small></div>")
399     end
400     end
401    
402     def viewcvsDiff(file)
403     if $viewcvsURL == nil || file.isBinary
404     "-&gt;"
405     else
406     "<a href=\"#{$viewcvsURL}#{urlEncode(file.path)}.diff?r1=#{file.fromVer}&r2=#{file.toVer}\">-&gt;</a>"
407     end
408     end
409    
410     class ModifiedFileHandler < FileHandler
411     def handleFile(file)
412     file.type="M"
413     file.fromVer=$fromVer
414     file.toVer=$toVer
415     print("<span class=\"pathname\">")
416     print(viewcvsPath(file.basedir))
417     println("<br /></span>")
418     println("<div class=\"fileheader\"><big><b>#{htmlEncode(file.file)}</b></big> <small id=\"info\">#{viewcvsFile(file.path,file.fromVer)} #{viewcvsDiff(file)} #{viewcvsFile(file.path,file.toVer)}</small></div>")
419     end
420     end
421    
422    
423     class UnifiedDiffStats
424     def initialize
425     @diffLines=3 # the three initial lines in the unidiff
426     end
427    
428     def diffLines
429     @diffLines
430     end
431    
432     def consume(line)
433     @diffLines += 1
434     case line[0,1]
435     when "+" then $file.lineAdditions += 1
436     when "-" then $file.lineRemovals += 1
437     end
438     end
439     end
440    
441     # TODO: change-within-line colourisation should really be comparing the
442     # set of lines just removed with the set of lines just added, but
443     # it currently considers just a single line
444    
445     class UnifiedDiffColouriser < LineConsumer
446     def initialize
447     @currentState = "@"
448     @currentStyle = "info"
449     end
450    
451     def output=(io)
452     @emailIO = io
453     end
454    
455     def consume(line)
456     initial = line[0,1]
457     if initial != @currentState
458     prefixLen = 1
459     suffixLen = 0
460     if initial=="+" && @currentState=="-" && @lineJustDeleted!=nil
461     # may be an edit, try to highlight the changes part of the line
462     a = line[1,line.length-1]
463     b = @lineJustDeleted[1,@lineJustDeleted.length-1]
464     prefixLen = commonPrefixLength(a, b)+1
465     suffixLen = commonPrefixLength(a.reverse, b.reverse)
466     # prevent prefix/suffux having overlap,
467     suffixLen = min(suffixLen, min(line.length,@lineJustDeleted.length)-prefixLen)
468     deleteInfixSize = @lineJustDeleted.length - (prefixLen+suffixLen)
469     addInfixSize = line.length - (prefixLen+suffixLen)
470     oversize_change = deleteInfixSize*100/@lineJustDeleted.length>33 || addInfixSize*100/line.length>33
471    
472     if prefixLen==1 && suffixLen==0 || deleteInfixSize<=0 || oversize_change
473     println(htmlEncode(@lineJustDeleted))
474     else
475     print(htmlEncode(@lineJustDeleted[0,prefixLen]))
476     print("<span id=\"removedchars\">")
477     print(formatChange(@lineJustDeleted[prefixLen,deleteInfixSize]))
478     print("</span>")
479     println(htmlEncode(@lineJustDeleted[@lineJustDeleted.length-suffixLen,suffixLen]))
480     end
481     @lineJustDeleted = nil
482     end
483     if initial=="-"
484     @lineJustDeleted=line
485     shift(initial)
486     # we'll print it next time (fingers crossed)
487     return
488     elsif @lineJustDeleted!=nil
489     println(htmlEncode(@lineJustDeleted))
490     @lineJustDeleted = nil
491     end
492     shift(initial)
493     if prefixLen==1 && suffixLen==0 || addInfixSize<=0 || oversize_change
494     encoded = htmlEncode(line)
495     else
496     encoded = htmlEncode(line[0,prefixLen]) +
497     "<span id=\"addedchars\">" +
498     formatChange(line[prefixLen,addInfixSize]) +
499     "</span>" +
500     htmlEncode(line[line.length-suffixLen,suffixLen])
501     end
502     else
503     encoded = htmlEncode(line)
504     end
505     if initial=="-"
506     unless @lineJustDeleted==nil
507     println(htmlEncode(@lineJustDeleted))
508     @lineJustDeleted=nil
509     end
510     end
511     if initial=="+"
512     if line =~ /\b(TODO\b.*)/
513     $todoList << $1
514     encoded.sub!(/\bTODO\b/, "<span id=\"TODO\">TODO</span>")
515     encoded = "<a name=\"todo#{$todoList.size}\" />#{encoded}"
516     elsif line =~ /\b(FIXME\b.*)/
517     $todoList << $1
518     encoded.sub!(/\FIXME\b/, "<span id=\"TODO\">FIXME</span>")
519     encoded = "<a name=\"todo#{$todoList.size}\" />#{encoded}"
520     end
521     # won't bother with /XXX/ (false positives more likely?)
522     end
523     println(encoded)
524     end
525    
526     def teardown
527     unless @lineJustDeleted==nil
528     println(htmlEncode(@lineJustDeleted))
529     @lineJustDeleted = nil
530     end
531     shift(nil)
532     end
533    
534     private
535    
536     def formatChange(text)
537     return '<small id="info">^M</small>' if text=="\r"
538     htmlEncode(text).gsub(/ /, '&nbsp;')
539     end
540    
541     def shift(nextState)
542     unless @currentState == nil
543     if @currentStyle == "info"
544     print("</small></pre>")
545     else
546     print("</pre>")
547     end
548     @currentStyle = case nextState
549     when "\\" then "info" # as in '\ No newline at end of file'
550     when "@" then "info"
551     when " " then "context"
552     when "+" then "added"
553     when "-" then "removed"
554     end
555     unless nextState == nil
556     if @currentStyle=='info'
557     print("<pre class=\"diff\"><small id=\"info\">")
558     else
559     print("<pre class=\"diff\" id=\"#{@currentStyle}\">")
560     end
561     end
562     end
563     @currentState = nextState
564     end
565    
566     def commonPrefixLength(a, b)
567     length = 0
568     a.each_byte do |char|
569     break unless b[length]==char
570     length = length + 1
571     end
572     return length
573     end
574     end
575    
576    
577     class UnifiedDiffHandler < LineConsumer
578     def setup
579     @stats = UnifiedDiffStats.new
580     if $file.removal? && $no_removed_file_diff
581     def self.consume(line)
582     @stats.consume(line) if lineno() > 3
583     end
584     return
585     end
586     @colour = UnifiedDiffColouriser.new
587     @colour.output = @emailIO
588     @lookahead = nil
589     # evil here:
590     def self.consume(line)
591     case lineno()
592     when 1
593     @diffline = line
594     when 2
595     @lookahead = line
596     when 3
597     println($fileHeaderHtml)
598     # TODO: move to UnifiedDiffColouriser
599     print("<pre class=\"diff\"><small id=\"info\">")
600     println(htmlEncode(@diffline)) # 'diff ...'
601     println(htmlEncode(@lookahead)) # '--- ...'
602     println(htmlEncode(line)) # '+++ ...'
603    
604     # now, redefine this method for the subsequent lines of input
605     def self.consume(line)
606     @stats.consume(line)
607     if @stats.diffLines < $maxLinesPerDiff
608     @colour.consume(line)
609     elsif @stats.diffLines == $maxLinesPerDiff
610     @colour.consume(line)
611     @colour.teardown
612     end
613     end
614     end
615     end
616     end
617    
618     def teardown
619     if @lookahead == nil
620     $file.isEmpty = true
621     elsif @lookahead =~ /Binary files .* and .* differ/
622     $file.isBinary = true
623     else
624     unless $file.removal? && $no_removed_file_diff
625     if @stats.diffLines > $maxLinesPerDiff
626     println("</pre>")
627     println("<strong class=\"error\">[truncated at #{$maxLinesPerDiff} lines; #{@stats.diffLines-$maxLinesPerDiff} more skipped]</strong>")
628     else
629     @colour.teardown
630     end
631     println("</div>") # end of "file" div
632     end
633     end
634     end
635     end
636    
637    
638    
639    
640    
641    
642    
643    
644    
645     $config = "#{ENV['CVSROOT']}/CVSROOT/cvsspam.conf"
646    
647     $debug = false
648     $recipients = Array.new
649     $sendmail_prog = "/usr/sbin/sendmail"
650     $no_removed_file_diff = false
651    
652     require 'getoptlong'
653    
654     opts = GetoptLong.new(
655     [ "--to", "-t", GetoptLong::REQUIRED_ARGUMENT ],
656     [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ],
657     [ "--debug", "-d", GetoptLong::NO_ARGUMENT ]
658     )
659    
660     opts.each do |opt, arg|
661     $recipients << arg if opt=="--to"
662     $config = arg if opt=="--config"
663     $debug = true if opt=="--debug"
664     end
665    
666    
667     if ARGV.length != 1
668     if ARGV.length > 1
669     $stderr.puts "extra arguments not needed: #{ARGV[1, ARGV.length-1].join(', ')}"
670     else
671     $stderr.puts "missing required file argument"
672     end
673     puts "Usage: cvsspam.rb [ --to <email> ] [ --config <file> ] <collect_diffs file>"
674     exit(-1)
675     end
676    
677     $logfile = ARGV[0]
678    
679    
680     $additionalHeaders = Array.new
681     $problemHeaders = Array.new
682    
683     # helper functions called from the 'config file'
684     def addHeader(name, value)
685     if name =~ /^[!-9;-~]+$/
686     $additionalHeaders << [name, value]
687     else
688     $problemHeaders << [name, value]
689     end
690     end
691     def addRecipient(email)
692     $recipients << email
693     end
694    
695     if FileTest.exists?($config)
696     load $config
697     else
698     blah("Config file '#{$config}' not found, ignoring")
699     end
700    
701     if $recipients.empty?
702     fail "No email recipients defined"
703     end
704    
705     $viewcvsURL << "/" if $viewcvsURL!=nil && $viewcvsURL!~/\/$/
706    
707    
708     unless $bugzillaURL == nil
709     commentSubstitutions['\b[Bb][Uu][Gg]\s*#?[0-9]+'] = bugSub
710     end
711     $commentEncoder = MultiSub.new(commentSubstitutions)
712    
713    
714     tagHandler = TagHandler.new
715    
716     $handlers = Hash[">" => CommentHandler.new,
717     "U" => UnifiedDiffHandler.new,
718     "T" => tagHandler,
719     "A" => AddedFileHandler.new,
720     "R" => RemovedFileHandler.new,
721     "M" => ModifiedFileHandler.new,
722     "V" => VersionHandler.new]
723    
724     $handlers["A"].setTagHandler(tagHandler)
725     $handlers["R"].setTagHandler(tagHandler)
726     $handlers["M"].setTagHandler(tagHandler)
727    
728     $fileEntries = Array.new
729     $todoList = Array.new
730    
731     File.open("#{$logfile}.emailtmp", File::RDWR|File::CREAT|File::TRUNC) do |mail|
732    
733     File.open($logfile) do |log|
734     reader = LogReader.new(log)
735    
736     until reader.eof
737     handler = $handlers[reader.currentLineCode]
738     if handler == nil
739     raise "No handler file lines marked '##{reader.currentLineCode}'"
740     end
741     handler.handleLines(reader.getLines, mail)
742     end
743     end
744     end
745    
746     if $subjectPrefix == nil
747     $subjectPrefix = "[CVS #{Repository.array.join(',')}]"
748     end
749 jonen 1.6 # HACK: put all file names at mail subject
750 jonen 1.11 all_files = ""
751 jonen 1.6 $fileEntries.each do |file|
752     name = htmlEncode(file.name_after_common_prefix)
753 jonen 1.11 if all_files != ""
754 jonen 1.10 all_files = all_files + ";" + name
755     else
756 jonen 1.6 all_files = name
757 jonen 1.1 end
758 jonen 1.6 end
759    
760 jonen 1.12 mailSubject = "#{$subjectPrefix} #{all_files}: #{$mailSubject}"
761 jonen 1.1 if mailSubject.length > $maxSubjectLength
762     mailSubject = mailSubject[0, $maxSubjectLength]
763     end
764    
765     blah("invoking #{$sendmail_prog} -t")
766     IO.popen("#{$sendmail_prog} -t", "w") do |mail|
767     mail.puts("To: #{$recipients.join(',')}")
768     mail.puts("Subject: #{mailSubject}")
769     mail.puts("Content-Type: text/html" + ($charset.nil? ? "" : "; charset=\"#{$charset}\""))
770     unless ($additionalHeaders.empty?)
771     $additionalHeaders.each do |header|
772     mail.puts("#{header[0]}: #{header[1]}")
773     end
774     end
775     mail.puts # end-of-headers
776    
777     mail.puts(<<HEAD)
778     <html>
779     <head>
780     <style><!--
781     body {background-color:#ffffff;}
782     .file {border:1px solid #eeeeee;margin-top:1em;margin-bottom:1em;}
783     .pathname {font-family:monospace; float:right;}
784     .fileheader {margin-bottom:.5em;}
785     .diff {margin:0;}
786     .todolist {padding:4px;border:1px dashed #000000;margin-top:1em;}
787     .todolist ul {margin-top:0;margin-bottom:0;}
788     tr.alt {background-color:#eeeeee}
789     #added {background-color:#ddffdd;}
790     #addedchars {background-color:#99ff99;font-weight:bolder;}
791     tr.alt #added {background-color:#ccf7cc;}
792     #removed {background-color:#ffdddd;}
793     #removedchars {background-color:#ff9999;font-weight:bolder;}
794     tr.alt #removed {background-color:#f7cccc;}
795     #info {color:#888888;}
796     #context {background-color:#eeeeee;}
797     td {padding-left:.3em;padding-right:.3em;}
798     tr.head {border-bottom-width:1px;border-bottom-style:solid;}
799     tr.head td {padding:0;padding-top:.2em;}
800     #todo {background-color:#ffff00;}
801     .comment {padding:4px;border:1px dashed #000000;background-color:#ffffdd}
802     .error {color:red;}
803     --></style>
804     </head>
805     <body>
806     HEAD
807    
808     unless ($problemHeaders.empty?)
809     mail.puts("<strong class=\"error\">Bad header format in '#{$config}':<ul>")
810     $stderr.puts("Bad header format in '#{$config}':")
811     $problemHeaders.each do |header|
812     mail.puts("<li><pre>#{htmlEncode(header[0])}</pre></li>")
813     $stderr.puts(" - #{header[0]}")
814     end
815     mail.puts("</ul></strong>")
816     end
817     mail.puts("<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"cols\">")
818     filesAdded = 0
819     filesRemoved = 0
820     filesModified = 0
821     totalLinesAdded = 0
822     totalLinesRemoved = 0
823     file_count = 0
824     lastPath = ""
825     last_repository = nil
826     $fileEntries.each do |file|
827     unless file.repository == last_repository
828     last_repository = file.repository
829     mail.print("<tr class=\"head\"><td colspan=\"#{$haveTag ? 5 : 4}\">")
830     mail.print("Commit in <b><tt>#{htmlEncode(last_repository.common_prefix)}</tt></b>")
831     mail.puts("</td></tr>")
832     end
833     file_count += 1
834     if (file_count%2==0)
835     mail.print("<tr class=\"alt\">")
836     else
837     mail.print("<tr>")
838     end
839     if file.addition?
840     filesAdded += 1
841     elsif file.removal?
842     filesRemoved += 1
843     elsif file.modification?
844     filesModified += 1
845     end
846     name = htmlEncode(file.name_after_common_prefix)
847     slashPos = name.rindex("/")
848     if slashPos==nil
849     prefix = ""
850     else
851     thisPath = name[0,slashPos]
852     name = name[slashPos+1,name.length]
853     if thisPath == lastPath
854     prefix = "&nbsp;"*(slashPos) + "/"
855     else
856     prefix = thisPath + "/"
857     end
858     lastPath = thisPath
859     end
860 jonen 1.6 if all_files == nil
861     all_files = name
862     else
863     all_files = all_files + " " + name
864     end
865 jonen 1.1 if file.addition?
866     name = "<span id=\"added\">#{name}</span>"
867     elsif file.removal?
868     name = "<span id=\"removed\">#{name}</span>"
869     end
870     if file.isEmpty || file.isBinary || (file.removal? && $no_removed_file_diff)
871     mail.print("<td><tt>#{prefix}#{name}&nbsp;</tt></td>")
872     else
873     mail.print("<td><tt>#{prefix}<a href=\"#file#{file_count}\">#{name}</a>&nbsp;</tt></td>")
874     end
875     if file.isEmpty
876     mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[empty]</small></td>")
877     elsif file.isBinary
878     mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[binary]</small></td>")
879     else
880     if file.lineAdditions>0
881     totalLinesAdded += file.lineAdditions
882     mail.print("<td align=\"right\" id=\"added\">+#{file.lineAdditions}</td>")
883     else
884     mail.print("<td>&nbsp;</td>")
885     end
886     if file.lineRemovals>0
887     totalLinesRemoved += file.lineRemovals
888     mail.print("<td align=\"right\" id=\"removed\">-#{file.lineRemovals}</td>")
889     else
890     mail.print("<td>&nbsp;</td>")
891     end
892     end
893     if file.tag
894     mail.print("<td>#{htmlEncode(file.tag)}</td>")
895     elsif $haveTag
896     mail.print("<td>&nbsp;</td>")
897     end
898     if file.addition?
899     mail.print("<td align=\"right\">added #{viewcvsFile(file.path,file.toVer)}</td>")
900     elsif file.removal?
901     mail.print("<td>#{viewcvsFile(file.path,file.fromVer)} removed</td>")
902     elsif file.modification?
903     mail.print("<td align=\"center\">#{viewcvsFile(file.path,file.fromVer)} #{viewcvsDiff(file)} #{viewcvsFile(file.path,file.toVer)}</td>")
904     end
905    
906 jonen 1.6 mail.puts("</tr>")
907 jonen 1.1 end
908 jonen 1.6
909 jonen 1.1 if $fileEntries.size>1
910     # give total number of lines added/removed accross all files
911     mail.print("<tr><td>&nbsp;</td>")
912     if totalLinesAdded>0
913     mail.print("<td align=\"right\" id=\"added\">+#{totalLinesAdded}</td>")
914     else
915     mail.print("<td>&nbsp;</td>")
916     end
917     if totalLinesRemoved>0
918     mail.print("<td align=\"right\" id=\"removed\">-#{totalLinesRemoved}</td>")
919     else
920     mail.print("<td>&nbsp;</td>")
921     end
922     mail.print("<td>&nbsp;</td>") if $haveTag
923     mail.puts("<td>&nbsp;</td></tr>")
924     end
925    
926     mail.puts("</table>")
927    
928     totalFilesChanged = filesAdded+filesRemoved+filesModified
929     if totalFilesChanged > 1
930     mail.print("<small id=\"info\">")
931     changeKind = 0
932     if filesAdded>0
933     mail.print("#{filesAdded} added")
934     changeKind += 1
935     end
936     if filesRemoved>0
937     mail.print(" + ") if changeKind>0
938     mail.print("#{filesRemoved} removed")
939     changeKind += 1
940     end
941     if filesModified>0
942     mail.print(" + ") if changeKind>0
943     mail.print("#{filesModified} modified")
944     changeKind += 1
945     end
946     mail.print(", total #{totalFilesChanged}") if changeKind > 1
947     mail.puts(" files</small><br />")
948     end
949    
950     todo_count = 0
951     $todoList.each do |item|
952     if todo_count == 0
953     mail.puts("<div class=\"todolist\"><ul>")
954     end
955     todo_count += 1
956     item = htmlEncode(item)
957     mail.puts("<li><a href=\"#todo#{todo_count}\">#{item}</a></li>")
958     end
959     mail.puts("</ul></div>") if todo_count>0
960    
961    
962     File.open("#{$logfile}.emailtmp") do |input|
963     input.each do |line|
964     mail.puts(line)
965     end
966     end
967     if $debug
968     blah("leaving file #{$logfile}.emailtmp")
969     else
970     File.unlink("#{$logfile}.emailtmp")
971     end
972    
973     mail.puts("</body></html>")
974    
975     end
976 jonen 1.7

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed