Seapine Labs
Personal tools

Python annotate

From Seapine Labs

Jump to: navigation, search

[edit] Annotate Script in Python

People sometimes want to see a view of a file which shows who edited each line. This is sometimes called annotate, blame or praise. Below is a (relatively) simple script written in python to produce such a view. You basically type in your command prompt

python annotate.py SurroundFile -oMyOutput.txt -ttext

to get an annotated view of your file. Your output will look something like

Version	       User	   Date	                      Code

1              dev1       12/20/2007               //I hope this works
2              dev2       1/1/2007                 //That didn't work
etc.

Save the following code in a file called annotate.py and save it somewhere in your path.

#!/usr/bin/python

import os,sys,getopt

class LineList:
	def __init__(self):
		self.data = []
		self.firstVersion = -1
		
	def InsertLine(self,newStartNum,newNumLines,newVersion):
		currentLength = len(self.data)
		if currentLength < newStartNum:
			self.data.extend([self.firstVersion]*(newStartNum-currentLength))
		for i in range(newNumLines):
			self.data.insert(newStartNum+i-1,newVersion)

	def DeleteLines(self,oldStartNum,oldNumLines):
		for i in range(oldNumLines):
			if len(self.data) >= (oldStartNum):
				del self.data[oldStartNum-1]

	def UpdateLine(self,startNum,numLines,newVersion):
		currentLength = len(self.data)
		if currentLength < (startNum+numLines):
			self.data.extend([self.firstVersion]*(startNum+numLines-currentLength))
		for i in range(numLines):
			self.data[startNum+i-1]=newVersion
	
	def CreateVersionList(self,fileName):
		for line in os.popen("sscm diffreport " + fileName + " -n0").readlines():
			if line.startswith("Record not found"):
				raise Usage("File does not appear to be in Surround")
			if  line.startswith("---"):
				oldVersion = int(line.split("version")[1].split(" in ")[0])
				if self.firstVersion == -1:
					self.firstVersion = oldVersion
			elif line.startswith("+++"):
				newVersion = int(line.split("version")[1].split(" in ")[0])
			elif line.startswith("@@"):
				changeLine = line.split("-")[1].split("+")
				lhs = changeLine[0].strip().split(",")
				oldStartNum = int(lhs[0])
				
				if len(lhs) == 2:
					oldNumLines = int(lhs[1])
				else:
					oldNumLines = 1
					
				rhs = changeLine[1].strip().rstrip("@").split(",")
				newStartNum = int(rhs[0])
				if len(rhs) == 2:
					newNumLines = int(rhs[1])
				else:
					newNumLines = 1
				
				if oldNumLines == 0:
					self.InsertLine(newStartNum,newNumLines,newVersion)
				elif oldNumLines == 1:
					if newNumLines == 0:
						self.DeleteLines(newStartNum,1)
					elif newNumLines == 1:
						self.UpdateLine(newStartNum,1,newVersion)
					else:
						self.UpdateLine(newStartNum,1,newVersion)
						self.InsertLine(newStartNum+1,newNumLines-1,newVersion)
				else:
					if oldStartNum == newStartNum and oldNumLines == newNumLines:
						self.UpdateLine(newStartNum,newNumLines,newVersion)
					else:
						if oldNumLines > newNumLines:
							self.UpdateLine(newStartNum,newNumLines,newVersion)
							self.DeleteLines(newStartNum+newNumLines,oldNumLines-newNumLines)
						else:
							self.UpdateLine(newStartNum,oldNumLines,newVersion)
							self.InsertLine(newStartNum+oldNumLines,newNumLines-oldNumLines,newVersion)
	
class HistoryList:
	def __init__(self):
		self.data = {}

	def CreateHistoryList(self,fileName):
		foundAction = False
		foundWholeLine = False
		tempLine = ""
		for line in os.popen("sscm history " + fileName).readlines():
			if line.startswith("add") or line.startswith("checkin") or line.startswith("promote") or line.startswith("rebase") or line.startswith("attach") or line.startswith("label") or line.startswith("rollback"):
				if len(line) > 60:
					tempLine = line.strip()
					foundWholeLine = True
				else:
					tempLine = line
			elif line.strip().startswith("Comments"):
				foundWholeLine = True
			else:
				tempLine = tempLine.strip() + "  " + line.strip()
			if		foundWholeLine:
				items = tempLine.split("  ")
				user = ""
				version = ""
				for col in items[1:]:
					if col != "":
						if user == "":
							user = col
						elif version == "":
							version = col
						else:
							date = col
							break
				foundWholeLine = False
				if len(version) > 0 and not (self.data.has_key(int(version))):
					self.data[int(version)] = user,date
				tempLine = ""

class Usage(Exception):
	def __init__(self, msg):
		self.msg = msg

def main(argv=None):
	if argv is None:
		argv = sys.argv
	try:
		try:
			if len(argv) < 2:
				raise Usage("usage: annotate FileName [-oOutputFileName] [-thtml-ttext]")
			fileName = sys.argv[1]
			if not os.path.isfile(fileName):
				raise Usage("usage: annotate FileName [-oOutputFileName] [-thtml-ttext]")
				
			cwd = os.getcwd()
			os.chdir(os.path.dirname(os.path.realpath(fileName)))
			fileName = os.path.basename(fileName)
			
			opts, args = getopt.getopt(argv[2:], "o:t:")
			outputName = ""
			htmlOutput = False
			for o,a in opts:
				if o == "-o":
					outputName = a
				if o == "-t":
					if a == "html":
						htmlOutput = True
			if outputName == "":
				if htmlOutput:
					outputName = "annotate.html"
				else:
					outputName = "annotate.txt"
		except getopt.error, msg:
			raise Usage(msg)
		
		lineVersions = LineList()
		lineVersions.CreateVersionList(fileName)
		
		historyData = HistoryList()
		historyData.CreateHistoryList(fileName)
		
		f = open(fileName)
		os.chdir(cwd)
		outFile = open(outputName,"w")
		try:
			if htmlOutput:
				outFile.writelines('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\m')
				outFile.writelines('<html xmlns="http://www.w3.org/1999/xhtml" ><head><meta content="text/html; charset=UTF-8" http-equiv="content-type"></head><body>')
				outFile.writelines('<table cellpadding="0" cellspacing="0" style="border-bottom:solid 1px black;width:100%; border-collapse: collapse;\
									 font-family: \'Courier New\', Courier, monospace; font-size: small;">')
				outFile.writelines('<thead><tr><th style="width:7%"></th><th style="width:7%"></th><th style="width:15%"></th><th style="width:71%"></th></tr></thead>')
			else:
				outFile.writelines("Version\tUser\tDate\tCode\n")
			lineCount=1
			totalNum = len(lineVersions.data)
			lastVersion = -1
			for line in f:
				if lineCount<totalNum:
					currentVersion = lineVersions.data[lineCount-1]
				else:
					currentVersion = lineVersions.firstVersion
				if htmlOutput:
					if lastVersion == currentVersion:
						border = "border-left:solid 1px black;border-right:solid 1px black"
						versionText = "<td></td><td></td><td></td>"
					else:
						border = "border-left:solid 1px black;border-right:solid 1px black;border-top:solid 1px black"
						lastVersion = currentVersion
						versionData = historyData.data[currentVersion]
						versionText = "<td>Version " + str(lastVersion) + "</td><td>User " + versionData[0] + "</td><td>Date " + versionData[1] + "</td>"
					outFile.writelines("<tr style='white-space:nowrap;text-overflow: ellipsis;" + border + "'>" + versionText + "<td style='" + border + "'>" + line + "</td></tr>")
				else:
					try:
						 versionData = historyData.data[currentVersion]
						 outFile.writelines(str(currentVersion) + "\t" + versionData[0] + "\t" + versionData[1] + "\t" + line.expandtabs())
					except KeyError, e:
						print "Missing version: " + str(currentVersion)
				lineCount += 1
			if htmlOutput:
				outFile.writelines('</table>')
				outFile.writelines('</body></html>')
		finally:
			 f.close()
		outFile.close()
		
	except Usage, err:
		print >>sys.stderr, err.msg
		return 2

if __name__ == "__main__":
	 sys.exit(main())



Note: Seapine does not provide support for sample scripts.














Issue Management Software | Source Code Control Software | Test Case Management