CVS Web Frontends
Index: cvsspam.rb
===================================================================
--- cvsspam.rb (.../tags/RELEASE-0_2_12) (revision 256)
+++ cvsspam.rb (.../trunk) (revision 256)
@@ -20,6 +20,7 @@
$version = "0.2.12"
+require 'time'
$maxSubjectLength = 200
$maxLinesPerDiff = 1000
@@ -35,10 +36,6 @@
a126 || b==UNDERSCORE || b==TAB
+ if b>126 || b==UNDERSCORE || b==TAB || b==HOOK || b==EQUALS
sprintf("=%02x", b)
elsif b == SPACE
"_"
@@ -163,8 +162,9 @@
# gives a string starting "=?", and including a charset specification, that
# marks the start of a quoted-printable character sequence
- def marker_start_quoted
- "=?#{@charset}?#{@encoding}?"
+ def marker_start_quoted(charset=nil)
+ charset = @charset if charset.nil?
+ "=?#{charset}?#{@encoding}?"
end
# test to see of the given string contains non-ASCII characters
@@ -388,6 +388,7 @@
class FileEntry
def initialize(path)
@path = path
+ @fromVer = @toVer = nil
@lineAdditions = @lineRemovals = 0
@repository = Repository.get(path)
@repository.merge_common_prefix(basedir())
@@ -533,6 +534,14 @@
# TODO: consolidate these into a nicer framework,
mailSub = proc { |match| "#{match}" }
urlSub = proc { |match| "#{match}" }
+gforgeTaskSub = proc { |match|
+ match =~ /([0-9]+)/
+ "#{match}"
+}
+gforgeBugSub = proc { |match|
+ match =~ /([0-9]+)/
+ "#{match}"
+}
bugzillaSub = proc { |match|
match =~ /([0-9]+)/
"#{match}"
@@ -544,11 +553,27 @@
match =~ /([0-9]+)/
"#{match}"
}
+issueSub = proc { |match|
+ match =~ /([0-9]+)/
+ "#{match}"
+}
wikiSub = proc { |match|
- match =~ /\[\[(.*)\]\]/
+ match =~ /\[\[(.*?)\]\]/
raw = $1
"[[#{raw}]]"
}
+xplannerIterationSub = proc { |match|
+ match =~ /([0-9]+)/
+ "#{match}"
+}
+xplannerProjectSub = proc { |match|
+ match =~ /([0-9]+)/
+ "#{match}"
+}
+xplannerStorySub = proc { |match|
+ match =~ /([0-9]+)/
+ "#{match}"
+}
commentSubstitutions = {
'(?:mailto:)?[\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+\b' => mailSub,
'\b(?:http|https|ftp):[^ \t\n<>"]+[\w/]' => urlSub
@@ -670,6 +695,12 @@
def diff(file)
'->'
end
+
+ # may be overridden by subclasses that are able to make a hyperlink to a
+ # history log for a file
+ def log(file)
+ ''
+ end
end
# Superclass for objects that can link to CVS frontends on the web (ViewCVS,
@@ -710,6 +741,14 @@
"#{super(file)}"
end
+ def log(file)
+ link = log_url(file)
+ if link
+ return "(log)"
+ end
+ return nil
+ end
+
protected
def add_repo(url)
if @repository_name
@@ -722,6 +761,10 @@
url
end
end
+
+ def log_url(file)
+ nil
+ end
end
# Link to ViewCVS
@@ -745,6 +788,15 @@
def diff_url(file)
add_repo("#{@base_url}#{urlEncode(file.path)}.diff?r1=#{file.fromVer}&r2=#{file.toVer}")
end
+
+ def log_url(file)
+ if file.toVer
+ log_anchor = "#rev#{file.toVer}"
+ else
+ log_anchor = ""
+ end
+ add_repo("#{@base_url}#{urlEncode(file.path)}#{log_anchor}")
+ end
end
# Link to Chora, from the Horde framework
@@ -767,9 +819,9 @@
class CVSwebFrontend < WebFrontend
def path_url(path, tag)
if tag == nil
- add_repo(@base_url + urlEncode(path))
+ add_repo(@base_url + urlEncode(path) + "/")
else
- add_repo("#{@base_url}#{urlEncode(path)}?only_with_tag=#{urlEncode(tag)}")
+ add_repo("#{@base_url}#{urlEncode(path)}/?only_with_tag=#{urlEncode(tag)}")
end
end
@@ -780,6 +832,17 @@
def diff_url(file)
add_repo("#{@base_url}#{urlEncode(file.path)}.diff?r1=text&tr1=#{file.fromVer}&r2=text&tr2=#{file.toVer}&f=h")
end
+
+ protected
+
+ def log_url(file)
+ if file.toVer
+ log_anchor = "#rev#{file.toVer}"
+ else
+ log_anchor = ""
+ end
+ add_repo("#{@base_url}#{urlEncode(file.path)}#{log_anchor}")
+ end
end
@@ -958,7 +1021,7 @@
end
shift(nil)
if @truncatedLineCount>0
- println("[Note: Some over-long lines of diff output only partialy shown]")
+ println("[Note: Some over-long lines of diff output only partially shown]")
end
end
@@ -1181,7 +1244,7 @@
# an RFC 822 email address
class EmailAddress
- def initialize(text)
+ def initialize(text, charset=nil)
if text =~ /^\s*([^<]+?)\s*<\s*([^>]+?)\s*>\s*$/
@personal_name = $1
@address = $2
@@ -1189,9 +1252,10 @@
@personal_name = nil
@address = text
end
+ @charset=charset
end
- attr_accessor :personal_name, :address
+ attr_accessor :personal_name, :address, :charset
def has_personal_name?
return !@personal_name.nil?
@@ -1222,7 +1286,7 @@
# rfc2047 encode the word, if it contains non-ASCII characters
def encode_word(word)
if $encoder.requires_rfc2047?(word)
- encoded = $encoder.marker_start_quoted
+ encoded = $encoder.marker_start_quoted(@charset)
$encoder.each_char_encoded(word) do |code|
encoded << code
end
@@ -1237,6 +1301,7 @@
cvsroot_dir = "#{ENV['CVSROOT']}/CVSROOT"
$config = "#{cvsroot_dir}/cvsspam.conf"
$users_file = "#{cvsroot_dir}/users"
+$users_file_charset = nil
$debug = false
$recipients = Array.new
@@ -1247,10 +1312,16 @@
$no_diff = false
$task_keywords = ['TODO', 'FIXME']
$bugzillaURL = nil
+$gforgeBugURL = nil
+$gforgeTaskURL = nil
$wikiURL = nil
$jiraURL = nil
$ticketURL = nil
+$issueURL = nil
$viewcvsURL = nil
+$xplannerIterationURL = nil
+$xplannerProjectURL = nil
+$xplannerStoryURL = nil
$choraURL = nil
$cvswebURL = nil
$from_address = nil
@@ -1261,6 +1332,7 @@
# 2MiB limit on attached diffs,
$mail_size_limit = 1024 * 1024 * 2
$arg_charset = nil
+$cvsroot_email_header = false
require 'getoptlong'
@@ -1353,17 +1425,35 @@
if $bugzillaURL != nil
- commentSubstitutions['\b[Bb][Uu][Gg]\s*#?[0-9]+'] = bugzillaSub
+ commentSubstitutions['\b[Bb]([Uu][Gg])?\s*[#:]?\s*\[?[0-9]+\]?'] = bugzillaSub
end
+if $gforgeBugURL != nil
+ commentSubstitutions['\B\[#[0-9]+\]'] = gforgeBugSub
+end
+if $gforgeTaskURL != nil
+ commentSubstitutions['\B\[[Tt][0-9]+\]'] = gforgeTaskSub
+end
if $jiraURL != nil
commentSubstitutions['\b[a-zA-Z]+-[0-9]+\b'] = jiraSub
end
if $ticketURL != nil
commentSubstitutions['\b[Tt][Ii][Cc][Kk][Ee][Tt]\s*#?[0-9]+\b'] = ticketSub
end
+if $issueURL != nil
+ commentSubstitutions['\b[Ii][Ss][Ss][Uu][Ee]\s*#?[0-9]+\b'] = issueSub
+end
if $wikiURL != nil
commentSubstitutions['\[\[.+\]\]'] = wikiSub
end
+if $xplannerIterationURL != nil
+ commentSubstitutions['\bXI\[?[0-9]+\]?'] = xplannerIterationSub
+end
+if $xplannerProjectURL != nil
+ commentSubstitutions['\bXP\[?[0-9]+\]?'] = xplannerProjectSub
+end
+if $xplannerStoryURL != nil
+ commentSubstitutions['\bXS\[?[0-9]+\]?'] = xplannerStorySub
+end
$commentEncoder = MultiSub.new(commentSubstitutions)
@@ -1546,11 +1636,14 @@
elsif file.removal?
name = "#{name}"
end
+ mail.print("")
if file.has_diff?
- mail.print(" | #{prefix}#{name} | ")
+ mail.print("#{prefix}#{name}")
else
- mail.print("#{prefix}#{name} | ")
+ mail.print("#{prefix}#{name}")
end
+ mail.print(" #{$frontend.log(file)}")
+ mail.print("")
if file.isEmpty
mail.print("[empty] | ")
elsif file.isBinary
@@ -1672,7 +1765,7 @@
io.each_line do |line|
if line =~ /^([^:]+)\s*:\s*(['"]?)([^\n\r]+)(\2)/
if email.address == $1
- return EmailAddress.new($3)
+ return EmailAddress.new($3, $users_file_charset)
end
end
end
@@ -1686,6 +1779,8 @@
# sensible header formatting, and for ensuring that the body is seperated
# from the message headers by a blank line (as it is required to be).
class MailContext
+ ENCODE_HEADERS = ["Subject", "X-CVSspam-Module-Path"]
+
def initialize(io)
@done_headers = false
@io = io
@@ -1695,8 +1790,8 @@
# called
def header(name, value)
raise "headers already commited" if @done_headers
- if name == "Subject"
- $encoder.encode_header(@io, "Subject", value)
+ if ENCODE_HEADERS.include?(name)
+ $encoder.encode_header(@io, name, value)
else
@io.puts("#{name}: #{value}")
end
@@ -1769,7 +1864,7 @@
ctx.header("To", recipients.map{|addr| addr.encoded}.join(','))
blah("Mail From: <#{from}>")
ctx.header("From", from.encoded) if from
- ctx.header("Date", Time.now.utc.strftime(DATE_HEADER_FORMAT))
+ ctx.header("Date", Time.now.rfc2822)
yield ctx
end
end
@@ -1800,10 +1895,10 @@
return unless $fileEntries.length == 1
file = $fileEntries[0]
name = zap_header_special_chars(file.path)
- unless file.fromVer == "NONE"
+ if file.fromVer
mail.header("References", make_msg_id("#{name}.#{file.fromVer}", $hostname))
end
- unless file.toVer == "NONE"
+ if file.toVer
mail.header("Message-ID", make_msg_id("#{name}.#{file.toVer}", $hostname))
end
end
@@ -1834,6 +1929,14 @@
end
end
mail.header("X-Mailer", "CVSspam #{$version} ")
+ if $cvsroot_email_header
+ mod = '/'
+ if Repository.count == 1
+ rep = Repository.array.first
+ mod << rep.common_prefix
+ end
+ mail.header("X-CVSspam-Module-Path", mod)
+ end
mail.body do |body|
make_html_email(body)