##
## Copyright (c) 2006 Jason Dillon
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##
##
## $Id: //guest/jason_dillon/p4spam/main/pylib/p4spam/htmlmessage.py#2 $ $Date: 2006/04/12 $
##
from p4spam import jiralinker
from p4spam.message import MessageBuilder
##
## HACK: Need config to be fixed!!!
##
from p4spam.config import *
class HtmlMessageBuilder(MessageBuilder):
def __init__(this, info):
MessageBuilder.__init__(this, info)
this.contentType = 'html'
def escapeLine(this, line):
##
## HACK: Need a library to handle all this mess
##
line = line.replace('&', '&')
line = line.replace('>', '>')
line = line.replace('<', '<')
##
## HACK: Trim the line here too... :-(
##
l = len(line)
if l > MAX_DIFF_LINE_LENGTH:
this.log.warn("Diff line longer than %s; truncating %s remaining characters" % (MAX_DIFF_LINE_LENGTH, l - MAX_DIFF_LINE_LENGTH))
line = line[:MAX_DIFF_LINE_LENGTH]
return line
def generatePathAnchor(this, path):
anchor = path
# Condense the path...
anchor = anchor.replace('/', '')
anchor = anchor.replace('.', '')
anchor = anchor.replace(' ', '')
return anchor
def getStyle(this):
return DEFAULT_STYLE
def writeDocument(this, buff):
buff.writeln('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"')
buff.writeln('"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">')
buff.writeln('<html xmlns="http://www.w3.org/1999/xhtml">')
this.writeHeader(buff)
this.writeBody(buff)
buff.writeln('</html>')
def writeHeader(this, buff):
buff.writeln('<head>')
buff.writeln("<style type=\"text/css\"><!--\n%s\n--></style>" % this.getStyle())
# Write user-header
if USER_HEADER != None:
buff.writeln(USER_HEADER)
buff.writeln("<title>%s</title>" % this.getTitle())
buff.writeln('</head>')
def writeOverview(this, buff):
# Change description header
buff.writeln('<dl>')
buff.writeln("<dt>Change</dt> <dd>%s</dd>" % this.info.getChange())
buff.writeln("<dt>Author</dt> <dd>%s</dd>" % this.info.getAuthor())
buff.writeln("<dt>Client</dt> <dd>%s</dd>" % this.info.getClient())
buff.writeln("<dt>Date</dt> <dd>%s</dd>" % this.info.getDateTime())
buff.writeln('</dl>')
buff.writeln()
# Change description/comment
buff.writeln('<h3>Description</h3>')
buff.writeln('<pre>')
lines = this.info.getComments()
for line in lines:
line = this.escapeLine(line)
line = jiralinker.render(line)
buff.writeln("%s<br/>" % line)
buff.writeln('</pre>')
buff.writeln()
# Link to change description URL
buff.writeln('<h3>URL</h3>')
url = CHANGE_LIST_URL % this.info.getChange()
buff.writeln("<a href=\"%s\">%s</a>" % (url, url))
##
## TODO: If this change fixes a job, include the job detail
##
def writeFileActionTOC(this, buff):
actions = ('edit', 'add', 'delete', 'branch', 'integrate')
actionLables = {
'edit': 'Edited Files',
'add': 'Added Files',
'delete': 'Deleted Files',
'branch': 'Branched Files',
'integrate': 'Integrated Files'
}
# Init the map
actionToFilesMap = {}
for a in actions:
actionToFilesMap[a] = []
# Fill the map
for f in this.info.getAffectedFiles():
actionToFilesMap[f.action].append(f)
this.log.debug("Action to files map: %s" % (actionToFilesMap))
for action in actions:
files = actionToFilesMap[action]
if len(files) == 0:
# Skip section if there are no files
this.log.debug("Skipping '%s' section; no files" % action)
continue
buff.writeln("<h3>%s</h3>" % actionLables[action])
buff.writeln('<ul>')
for f in files:
buff.writeln('<li>')
anchor = this.generatePathAnchor(f.path)
pathspec = "%s#%s" % (f.path, f.rev)
link = "<a href=\"%s\">[history]</a>" % (FILE_HISTORY_URL % f.path)
buff.writeln("<a href=\"#%s\">%s</a> %s" % (anchor, pathspec, link))
buff.writeln('</li>')
buff.writeln('</ul>')
buff.writeln()
def writeBody(this, buff):
buff.writeln('<body>')
# Write user-body-header
if USER_BODY_HEADER != None:
buff.writeln(USER_BODY_HEADER)
buff.writeln('<div id="msg">')
this.writeOverview(buff)
buff.writeln()
this.writeFileActionTOC(buff)
buff.writeln('</div>')
buff.writeln()
this.writeDifferences(buff)
buff.writeln()
# Write user-body-footer
if USER_BODY_FOOTER != None:
buff.writeln(USER_BODY_FOOTER)
buff.writeln('</body>')
def writeDifferences(this, buff):
buff.writeln('<div id="patch">')
buff.writeln('<h3>Differences</h3>')
for diff in this.info.getDifferences():
# Write the anchor
anchor = this.generatePathAnchor(diff.path)
buff.writeln("<a id=\"%s\"></a>" % anchor)
buff.writeln('<div class="modfile">')
buff.writeln("<h4>%s#%s (%s)</h4>" % (diff.path, diff.rev, diff.filetype))
buff.writeln('<pre class="diff">')
i = 0
maxlines = MAX_DIFF_LINES # TODO: Get from config
style = None
lastStyle = None
for line in diff.lines:
line = this.escapeLine(line)
if line.startswith('@@'):
style = 'lines'
elif line.startswith('+'):
style = 'add'
elif line.startswith('-'):
style = 'rem'
else:
style = 'cx'
# Only write style spans if the sytle was changed
if style != lastStyle:
if lastStyle != None:
buff.write('</span>')
buff.write('<span class="%s">' % style)
buff.write(line)
lastStyle = style
i = i + 1
if i >= maxlines:
break
# Need to close up the last span
buff.writeln('</span>')
buff.writeln('</pre>')
# If we truncated, then spit out a message
totallines = len(diff.lines)
if i >= maxlines and totallines != maxlines:
buff.write('<strong class="error">')
buff.write("Truncated at %s lines; %s more skipped" % (maxlines, totallines - maxlines))
buff.writeln('</strong>')
buff.writeln('</div>')
buff.writeln('</div>')