## ## 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("" % this.info.getChange()) buff.writeln("" % this.info.getAuthor()) buff.writeln("" % this.info.getClient()) buff.writeln("" % this.info.getDateTime()) buff.writeln('
Change%s
Author%s
Client%s
Date%s
') 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('') 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('') 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('')