/[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.10 - (hide annotations)
Sun Jan 19 01:30:23 2003 UTC (21 years, 5 months ago) by jonen
Branch: MAIN
Changes since 1.9: +3 -4 lines
+ clean up

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     $fileEntries.each do |file|
751     name = htmlEncode(file.name_after_common_prefix)
752 jonen 1.10 if defined? all_files
753     all_files = all_files + ";" + name
754     else
755 jonen 1.6 all_files = name
756 jonen 1.1 end
757 jonen 1.6 end
758    
759     mailSubject = "#{$subjectPrefix} #{all_files} #{$mailSubject}"
760 jonen 1.1 if mailSubject.length > $maxSubjectLength
761     mailSubject = mailSubject[0, $maxSubjectLength]
762     end
763    
764     blah("invoking #{$sendmail_prog} -t")
765     IO.popen("#{$sendmail_prog} -t", "w") do |mail|
766     mail.puts("To: #{$recipients.join(',')}")
767     mail.puts("Subject: #{mailSubject}")
768     mail.puts("Content-Type: text/html" + ($charset.nil? ? "" : "; charset=\"#{$charset}\""))
769     unless ($additionalHeaders.empty?)
770     $additionalHeaders.each do |header|
771     mail.puts("#{header[0]}: #{header[1]}")
772     end
773     end
774     mail.puts # end-of-headers
775    
776     mail.puts(<<HEAD)
777     <html>
778     <head>
779     <style><!--
780     body {background-color:#ffffff;}
781     .file {border:1px solid #eeeeee;margin-top:1em;margin-bottom:1em;}
782     .pathname {font-family:monospace; float:right;}
783     .fileheader {margin-bottom:.5em;}
784     .diff {margin:0;}
785     .todolist {padding:4px;border:1px dashed #000000;margin-top:1em;}
786     .todolist ul {margin-top:0;margin-bottom:0;}
787     tr.alt {background-color:#eeeeee}
788     #added {background-color:#ddffdd;}
789     #addedchars {background-color:#99ff99;font-weight:bolder;}
790     tr.alt #added {background-color:#ccf7cc;}
791     #removed {background-color:#ffdddd;}
792     #removedchars {background-color:#ff9999;font-weight:bolder;}
793     tr.alt #removed {background-color:#f7cccc;}
794     #info {color:#888888;}
795     #context {background-color:#eeeeee;}
796     td {padding-left:.3em;padding-right:.3em;}
797     tr.head {border-bottom-width:1px;border-bottom-style:solid;}
798     tr.head td {padding:0;padding-top:.2em;}
799     #todo {background-color:#ffff00;}
800     .comment {padding:4px;border:1px dashed #000000;background-color:#ffffdd}
801     .error {color:red;}
802     --></style>
803     </head>
804     <body>
805     HEAD
806    
807     unless ($problemHeaders.empty?)
808     mail.puts("<strong class=\"error\">Bad header format in '#{$config}':<ul>")
809     $stderr.puts("Bad header format in '#{$config}':")
810     $problemHeaders.each do |header|
811     mail.puts("<li><pre>#{htmlEncode(header[0])}</pre></li>")
812     $stderr.puts(" - #{header[0]}")
813     end
814     mail.puts("</ul></strong>")
815     end
816     mail.puts("<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"cols\">")
817     filesAdded = 0
818     filesRemoved = 0
819     filesModified = 0
820     totalLinesAdded = 0
821     totalLinesRemoved = 0
822     file_count = 0
823     lastPath = ""
824     last_repository = nil
825     $fileEntries.each do |file|
826     unless file.repository == last_repository
827     last_repository = file.repository
828     mail.print("<tr class=\"head\"><td colspan=\"#{$haveTag ? 5 : 4}\">")
829     mail.print("Commit in <b><tt>#{htmlEncode(last_repository.common_prefix)}</tt></b>")
830     mail.puts("</td></tr>")
831     end
832     file_count += 1
833     if (file_count%2==0)
834     mail.print("<tr class=\"alt\">")
835     else
836     mail.print("<tr>")
837     end
838     if file.addition?
839     filesAdded += 1
840     elsif file.removal?
841     filesRemoved += 1
842     elsif file.modification?
843     filesModified += 1
844     end
845     name = htmlEncode(file.name_after_common_prefix)
846     slashPos = name.rindex("/")
847     if slashPos==nil
848     prefix = ""
849     else
850     thisPath = name[0,slashPos]
851     name = name[slashPos+1,name.length]
852     if thisPath == lastPath
853     prefix = "&nbsp;"*(slashPos) + "/"
854     else
855     prefix = thisPath + "/"
856     end
857     lastPath = thisPath
858     end
859 jonen 1.6 if all_files == nil
860     all_files = name
861     else
862     all_files = all_files + " " + name
863     end
864 jonen 1.1 if file.addition?
865     name = "<span id=\"added\">#{name}</span>"
866     elsif file.removal?
867     name = "<span id=\"removed\">#{name}</span>"
868     end
869     if file.isEmpty || file.isBinary || (file.removal? && $no_removed_file_diff)
870     mail.print("<td><tt>#{prefix}#{name}&nbsp;</tt></td>")
871     else
872     mail.print("<td><tt>#{prefix}<a href=\"#file#{file_count}\">#{name}</a>&nbsp;</tt></td>")
873     end
874     if file.isEmpty
875     mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[empty]</small></td>")
876     elsif file.isBinary
877     mail.print("<td colspan=\"2\" align=\"center\"><small id=\"info\">[binary]</small></td>")
878     else
879     if file.lineAdditions>0
880     totalLinesAdded += file.lineAdditions
881     mail.print("<td align=\"right\" id=\"added\">+#{file.lineAdditions}</td>")
882     else
883     mail.print("<td>&nbsp;</td>")
884     end
885     if file.lineRemovals>0
886     totalLinesRemoved += file.lineRemovals
887     mail.print("<td align=\"right\" id=\"removed\">-#{file.lineRemovals}</td>")
888     else
889     mail.print("<td>&nbsp;</td>")
890     end
891     end
892     if file.tag
893     mail.print("<td>#{htmlEncode(file.tag)}</td>")
894     elsif $haveTag
895     mail.print("<td>&nbsp;</td>")
896     end
897     if file.addition?
898     mail.print("<td align=\"right\">added #{viewcvsFile(file.path,file.toVer)}</td>")
899     elsif file.removal?
900     mail.print("<td>#{viewcvsFile(file.path,file.fromVer)} removed</td>")
901     elsif file.modification?
902     mail.print("<td align=\"center\">#{viewcvsFile(file.path,file.fromVer)} #{viewcvsDiff(file)} #{viewcvsFile(file.path,file.toVer)}</td>")
903     end
904    
905 jonen 1.6 mail.puts("</tr>")
906 jonen 1.1 end
907 jonen 1.6
908 jonen 1.1 if $fileEntries.size>1
909     # give total number of lines added/removed accross all files
910     mail.print("<tr><td>&nbsp;</td>")
911     if totalLinesAdded>0
912     mail.print("<td align=\"right\" id=\"added\">+#{totalLinesAdded}</td>")
913     else
914     mail.print("<td>&nbsp;</td>")
915     end
916     if totalLinesRemoved>0
917     mail.print("<td align=\"right\" id=\"removed\">-#{totalLinesRemoved}</td>")
918     else
919     mail.print("<td>&nbsp;</td>")
920     end
921     mail.print("<td>&nbsp;</td>") if $haveTag
922     mail.puts("<td>&nbsp;</td></tr>")
923     end
924    
925     mail.puts("</table>")
926    
927     totalFilesChanged = filesAdded+filesRemoved+filesModified
928     if totalFilesChanged > 1
929     mail.print("<small id=\"info\">")
930     changeKind = 0
931     if filesAdded>0
932     mail.print("#{filesAdded} added")
933     changeKind += 1
934     end
935     if filesRemoved>0
936     mail.print(" + ") if changeKind>0
937     mail.print("#{filesRemoved} removed")
938     changeKind += 1
939     end
940     if filesModified>0
941     mail.print(" + ") if changeKind>0
942     mail.print("#{filesModified} modified")
943     changeKind += 1
944     end
945     mail.print(", total #{totalFilesChanged}") if changeKind > 1
946     mail.puts(" files</small><br />")
947     end
948    
949     todo_count = 0
950     $todoList.each do |item|
951     if todo_count == 0
952     mail.puts("<div class=\"todolist\"><ul>")
953     end
954     todo_count += 1
955     item = htmlEncode(item)
956     mail.puts("<li><a href=\"#todo#{todo_count}\">#{item}</a></li>")
957     end
958     mail.puts("</ul></div>") if todo_count>0
959    
960    
961     File.open("#{$logfile}.emailtmp") do |input|
962     input.each do |line|
963     mail.puts(line)
964     end
965     end
966     if $debug
967     blah("leaving file #{$logfile}.emailtmp")
968     else
969     File.unlink("#{$logfile}.emailtmp")
970     end
971    
972     mail.puts("</body></html>")
973    
974     end
975 jonen 1.7

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