##
## 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: htmlmessage.py 82 2009-08-26 09:26:36Z mindwanderer $
##
from p4spam import config, jiralinker
from p4spam.message import MessageBuilder
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('<', '<')
line = line.replace('\t', ' ')
##
## HACK: Trim the line here too... :-(
##
l = len(line)
m = config.MAX_DIFF_LINE_LENGTH
if l > m:
this.log.warning("Diff line longer than %s; truncating %s remaining characters" % (m, l - m))
line = line[:m]
return line
# parses the Description of the changelist. Separate from above, generic escapeLine because
# perhaps we want to display the entire Description no matter the length and not invoke
# MAX_DIFF_LINE_LENGTH to cut it off
def escapeDescLine(this, line):
##
## HACK: Need a library to handle all this mess
##
line = line.replace('&', '&')
line = line.replace('>', '>')
line = line.replace('<', '<')
line = line.replace('\t', ' ')
return line
def generatePathAnchor(this, path):
anchor = path
# Condense the path...
anchor = anchor.replace('/', '')
anchor = anchor.replace('.', '')
anchor = anchor.replace(' ', '')
return anchor
def writeDocument(this, buff, recipient):
buff.writeln('')
buff.writeln('')
this.writeHeader(buff)
this.writeBody(buff, recipient)
buff.writeln('')
def writeHeader(this, buff):
buff.writeln('
')
buff.writeln("" % config.HTML_STYLE)
# Write user-header
if config.USER_HEADER != None:
buff.writeln(config.USER_HEADER)
buff.writeln("%s" % this.getTitle())
buff.writeln('')
def writeOverview(this, buff):
# Change description header
buff.writeln('')
buff.writeln("Change | %s |
" % this.info.getChange())
buff.writeln("Author | %s |
" % this.info.getAuthor())
buff.writeln("Client | %s |
" % this.info.getClient())
buff.writeln("Date | %s |
" % this.info.getDateTime())
buff.writeln('
')
buff.writeln()
# Change description/comment
buff.writeln('Description
')
buff.writeln('')
lines = this.info.getComments()
for line in lines:
line = this.escapeDescLine(line)
line = jiralinker.render(line)
buff.writeln("%s
" % line)
buff.writeln('
')
buff.writeln()
# Link to change description URL
buff.writeln('URL
')
url = config.CHANGE_LIST_URL % this.info.getChange()
buff.writeln("%s" % (url, url))
# If this change fixes a job, include the job detail
fixes = this.info.getJobsFixed()
if len(fixes) != 0:
buff.writeln('Jobs Fixed
')
buff.writeln('')
for fix in fixes:
# Link to P4Web if configured
if config.P4WEB_JOB_VIEW_URL != None:
fixurl = config.P4WEB_JOB_VIEW_URL % fix.name
fixlink = '%s' % (fixurl, fix.name)
else:
fixlink = fix.name
buff.write('- %s on %s by %s %s
' % (fixlink, fix.date, fix.author, fix.status))
##
## NOTE: Ignore the Job description for now.
##
buff.writeln('
')
def writeFileActionTOC(this, buff):
actions = ('edit', 'add', 'delete', 'branch', 'integrate', 'purge', 'move/delete', 'move/add', 'import')
actionLables = {
'edit': 'Edited Files',
'add': 'Added Files',
'delete': 'Deleted Files',
'branch': 'Branched Files',
'integrate': 'Integrated Files',
'purge': 'Purged Files',
'move/delete': 'Moved Files (delete)',
'move/add': 'Moved Files (add)',
'import': 'Imported Files (add from remote depot)'
}
# 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("%s
" % actionLables[action])
buff.writeln('')
fileslimit = 0
for f in files:
buff.writeln('- ')
anchor = this.generatePathAnchor(f.path)
pathspec = "%s#%s" % (f.path, f.rev)
link = "[history]" % (config.FILE_HISTORY_URL % f.path)
buff.writeln("%s %s" % (anchor, pathspec, link))
buff.writeln('
')
fileslimit = fileslimit+1
if fileslimit >= config.MAX_FILE_LIST:
buff.write('')
buff.write('File list truncated at %s by configuration' % config.MAX_FILE_LIST)
buff.write('')
break
buff.writeln('
')
buff.writeln()
def writeBody(this, buff, recipient):
buff.writeln('')
# Write user-body-header
if config.USER_BODY_HEADER != None:
buff.writeln(config.USER_BODY_HEADER)
buff.writeln('')
this.writeOverview(buff)
buff.writeln()
this.writeFileActionTOC(buff)
buff.writeln('
')
buff.writeln()
this.writeDifferences(buff, recipient)
buff.writeln()
# Write user-body-footer
if config.USER_BODY_FOOTER != None:
buff.writeln(config.USER_BODY_FOOTER)
buff.writeln('')
def writeDifferences(this, buff, recipient):
buff.writeln('')
buff.writeln('
Differences
')
fileslimit = 0
# log the recipient into p4 as the admin so we can masquerade as him/her
loginresult = this.p4.raw('login', recipient)
for diff in this.info.getDifferences():
# Write the anchor
anchor = this.generatePathAnchor(diff.path)
buff.writeln("
" % anchor)
buff.writeln('
')
buff.writeln("
%s#%s (%s)
" % (diff.path, diff.rev, diff.filetype))
# this is where we'll take the recipient then decide whether or not they have
# permissions to view this diff
# try to print the file as the recipient
syncresult = this.p4.raw('-u', recipient, 'print', diff.path)
# if the 'p4 print' results are empty, it means the output went to stderr, which
# is to say this user doesn't have access to that file
if len(syncresult) == 0:
buff.write('')
buff.write("You do not have appropriate Perforce permissions to view this diff!")
buff.write('')
buff.writeln('')
# Allow some filetypes to not diff
if diff.filetype in config.DISABLE_DIFF_FOR_FILETYPE:
buff.write('
')
buff.write('Diff disabled for type %s by configuration' % diff.filetype)
buff.write('')
else:
buff.writeln('
')
i = 0
maxlines = config.MAX_DIFF_LINES
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('')
buff.write('' % style)
buff.write(line)
lastStyle = style
i = i + 1
if i >= maxlines:
break
# Need to close up the last span
buff.writeln('')
buff.writeln('
')
# If we truncated, then spit out a message
totallines = len(diff.lines)
if i >= maxlines and totallines != maxlines:
buff.write('
')
buff.write("Truncated at %s lines; %s more skipped" % (maxlines, totallines - maxlines))
buff.writeln('')
buff.writeln('
')
fileslimit = fileslimit + 1
if fileslimit >= config.MAX_DIFF_FILES:
buff.write('')
buff.write('Diff section truncated at %s files by configuration' % config.MAX_DIFF_FILES)
buff.write('')
break
buff.writeln('')