#!/usr/bin/env python

###############################################################################
#
# CodeYard Subversion Highscores
#
#	This script parses the subversion logfiles of a number of repositories,
#	sorts the output and produces easily parsable output.
#
#	Begin: 12-04-2006
#
#	(c) 2006 sebas@codeyard.net
#
#	License: GPL v2
#
###############################################################################

"""
TODO:
	* Refine comparison functions: if a.commits == a.commits -> compare by log, 
	  and the other way round.
	* Commandline options
	* Daterange for svn commands
	* Do svn update in advance of parsing the logfile
"""
import os, sys
from Graph import *
import Colors

__version__ = "0.12."
__date__ = "April, 5th, 2008"

svn_up = False

__doc__ = """Help for %s : CodeYard subversion log analyser
v%s, %s

--help		Show this help
--readable	Print output in human-readable form
--debug		Show verbose debugging messages
--log		Sort output by number of logmessages (default: number of commits)
""" % (sys.argv[0], __version__, __date__)


if "--help" in sys.argv:
	print __doc__
	sys.exit(0)

if "--debug" in sys.argv:
	DEBUG = True
else:
	DEBUG = False

if "--readable" in sys.argv:
	output_readable = True # Set to false or parsable output
else:
	output_readable = False # Set to false or parsable output

if "--log" in sys.argv:
	compare_by = "logs"
else:
	compare_by = "commits" # Set to false for comparison by logmessages

class Author:
	""" An author has scores for number of commits, number of logmessages and
		obviously a name.
	"""
	def __init__(self):
		self.name = ""
		self.commits = 0
		self.logs = 0
		
	def show(self):
		""" Nice, readable output."""
		print ">> Author: ", self.name
		print "   Commits:", self.commits
		print "   Logs:   ", self.logs
		print
		
	def info(self):
		""" Easily parsable output in one line:
			name commits logmessages """
		print self.name, self.commits, self.logs


class CyProject:
	""" A CY Project holds information about who committed how much
		and of course 'whatnot' """
		
	#define XML Tags.
	logentry_begin, logentry_end = "<logentry", "</logentry>\n"
	author_begin, author_end = "<author>", "</author>\n"
	msg_begin, msg_end = "<msg>", "</msg>"		
	logs = 0
	commits = 0

	
	def __init__(self, working_copies,filters):
		# Our dict with authors in it, hashed by name.
		self.authors = {}
		self.filters = filters
		for working_copy in working_copies:
			if DEBUG: print "Parsing ", working_copy
			self.parseLog(working_copy)
		
	def parseLog(self, working_copy):
		""" Read file line by line and fill the authors list. """
		if working_copy[-4:] == ".xml":
			if DEBUG: print "Using XML file:", working_copy
			svn_log = open(working_copy)
		else:
			svn_cmd = "svn log %s --xml" % working_copy
			if DEBUG: print svn_cmd
			svn_log = os.popen(svn_cmd)

		EOF = False
		while not EOF:
			line = svn_log.readline()
			if len(line):
				if line.find(self.logentry_begin) > -1:
					# New logentry
					author = Author()
					log_msg = False
				elif line.find(self.author_begin) > -1:
					author.name = line[len('<author>'):line.find("</author>")]
					#if DEBUG: print "Commit by: " + author.name
				elif line.find(self.msg_begin) > -1:
					if line.find(self.msg_end) == len(self.msg_begin):
						# No Logmessage
						log_msg = False
					else:
						# non-empty logmessage
						log_msg = True
				elif line == self.logentry_end:
					# Push author into dict, if not already in there:
					if author.name not in self.authors.keys():
						self.authors[author.name] = author
					if log_msg:
						self.authors[author.name].logs += 1
					self.authors[author.name].commits += 1
			else:
				EOF = True
		svn_log.close()
				
	def listAuthors(self,authors=None,show=True):
		if not authors:
			authors = self.authors.values()
		for a in authors:
			if a.name not in self.filters:
				if show:
					if not output_readable: a.info()
					else: a.show()
				self.commits = self.commits + a.commits
				self.logs = self.logs + a.logs
		if show: print "Total:", self.commits, self.logs
		
		
	def hiScoreTable(self,order="commits",show=True):
		""" Returns a sorted list of authors, by default sorted by number of 	
			commits. Other possible sorts are "log", ..."""
		
		# First define comparison functions for our objects:
		def cmpCommits(author1, author2):
			""" Compare the number of commits, if it's the same, compare logs. """
			s = cmp(author1.commits, author2.commits)
			if s == 0:
				return cmp(author1.logs, author2.logs)
			else: return s
				
		def cmpLogs(author1, author2):
			""" Compare the number of logs, if it's the same, compare commits."""
			s = cmp(author1.logs, author2.logs)
			if s == 0:
				return cmp(author1.commits, author2.commits)
			else: return s

		# Choose the right comparison function to go on with
		if order is "commits": mycmp = cmpCommits
		elif order is "logs":  mycmp = cmpLogs
		
		authors = self.authors.values()
		
		#for n in authors:
		#	print n.name, n.commits
		#print "------------"
		authors.sort(mycmp)
		authors.reverse()
		self.sorted_authors = []
		for n in authors:
			self.sorted_authors.append(n.name)
			#print n.name, n.commits
		#self.authors = sorted_authors
		#for n in self.sorted_authors:
		#	print "S:",self.authors[n].name,self.authors[n].commits
		##print self.authors
		self.listAuthors(self.authors.values(), show)
		return

class SvnChart:
	""" Creates an image showing statistics of a SVN repository. """
	
	def __init__(self,project_path,project_name=False):
		self.project_path = project_path
		if not project_name:
			if project_path[-1] == "/":
				project_path = project_path[:-1]
			self.project_name = project_path.split("/")[-1]
		else:
			self.project_name = project_name
		self.title = "SVN Commits: "+self.project_name
		self.filters = []
		
		Color = Colors.Colors()
		self.colorindex = 0
		self.cls = (Color.DarkBlue, Color.DarkRed, Color.DarkGreen, 
					Color.DarkOrange, Color.IndianRed,
					Color.SteelBlue, Color.OrangeRed)
		self.colorpairs = [(Color.DarkBlue, Color.SlateBlue1),
							(Color.forestgreen, Color.lightgreen),
							(Color.khaki, Color.IndianRed),
							(Color.DarkCyan, Color.DeepPink4),
							(Color.orchid, Color.purple)
								]
		self.bg_image = "cy_logo.png"
	
	def getAColor(self):
		if self.colorindex == len(self.cls): self.colorindex = 0
		
		color = self.cls[self.colorindex]
		self.colorindex += 1
		return color
		
	def setBgImage(self,image):
		self.bg_image = image
		
	def filterUsers(self,userlist):
		""" Do not show these users in the graphic"""
		self.filters.extend(userlist)
		
	def createCsv(self, dir="logs/", name="kde-svn-"):
		#slp = CyProject([self.project_path],self.filters)
		#slp.hiScoreTable(compare_by, False)
		self.years = (2004, 2005, 2006, 2007, 2008)
		#for years in self.years:
		self.months = range(1,13)
		first = []
		first.append("")
		for year in self.years:
			for month in self.months:
				first.append("%s.%s" % (year, month))
		outfile = "svnstats.csv"
		fhandle = open(outfile, "w")
		fhandle.write(";".join(first)+"\n")

		for project in ("kde", "gnome"):
			if project == "kde":
				dir="logs/"
				name="kde-svn-"
				prefix = "KDE"
			if project == "gnome":
				dir="logs/gnome/"
				name="gnome-svn-"
				prefix = "GNOME"
			#year = 2004
			commits = {}
			authors = {}
			for year in self.years:
				commits[year] = {}
				authors[year] = {}
				for month in self.months:
					log_file = "%s%s%i-%i.xml" % (dir, name, + year, month)
					print "Reading ... " + log_file
					if os.path.isfile(log_file):
						slp = CyProject([log_file],self.filters)
						slp.hiScoreTable(compare_by, False)
						commits[year][month] = slp.commits
						authors[year][month] = len(slp.authors)
						# mean commits/author
					else:
						print "logfile " + log_file + "does not exist"
						commits[year][month] = 0
						authors[year][month] = 0
			line_commits  = []
			line_commits.append(prefix+ " commits")
			line_authors = []
			line_authors.append(prefix+ " authors")
			
			for year in self.years:
				for month in self.months:
					line_commits.append(str(commits[year][month]))
					line_authors.append(str(authors[year][month]))
			fhandle.write(";".join(line_commits)+"\n")
			fhandle.write(";".join(line_authors)+"\n")
		##fhandle.write(";".join(first)+"\n")
		print ";".join(first)
		print ";".join(line_commits)
		
		fhandle.close()
				
		
		print commits
		#for
	
	def createGraph(self):
		slp = CyProject([self.project_path],self.filters)
		slp.hiScoreTable(compare_by, False)
		
		self.barchart = BarChartImage(self.title,self.bg_image)
		self.barchart.bar_order = slp.sorted_authors
		#self.barchart.subTitle("%i commits, %s%% met commentaar" % (slp.commits,
		#	 str(round((slp.logs*1.0/slp.commits*100),1))))
		self.barchart.subTitle("%i commits by %i distinct authors" % (slp.commits, len(slp.authors)))
		print str(slp.commits) + " commits by " + str(len(slp.authors))
		i = 0
		for fn in self.filters:
			try:
				slp.sorted_authors.remove(fn)
			except ValueError:
				pass
				
		for n in slp.sorted_authors:
			#print "Graphing", n
			a = slp.authors[n]
			light = self.colorpairs[i][0]
			dark = self.colorpairs[i][1]
			i += 1
			if len(self.colorpairs) <= i:
				i = 0
			
			if a.name not in  self.filters:
				c_perc = (a.commits * 1.00) / slp.commits
				a_perc = (a.logs * 1.00) / slp.commits
				#a_perc =  slp.commits
				label = "%s"  % a.commits
				self.barchart.addBar(a.name, c_perc, light, label)
				self.barchart.addBar(a.name, a_perc, dark)
				
		outfile = "graphs/" + self.project_name.replace(" ", "_") + ".png"
		
		self.barchart.drawImage(outfile)
		self.bars = []


if __name__ == "__main__":
	projects = [
		"/home/sebas/work/projecten-codeyard/Grabbl/",
		"/home/sebas/work/projecten-codeyard/SPELO/",
		"/home/sebas/work/projecten-codeyard/GravityGames/",
		"/home/sebas/work/projecten-codeyard/GameDesigner/",
		"/home/sebas/work/projecten-codeyard/Sokoban3D/",
	]
	
	#projects = [
		#"/home/sebas/work/codeyard/admin/",
		#"/home/sebas/work/codeyard/website/",
	#	"/home/sebas/dev/guidance/"
	#]
	projects =  ["svn+ssh://svn.kde.org/home/kde/branches/KDE/4.0/"]
	xml_logs = ["logs/4_0-branch.xml"]
	#xml_logs = ["logs/gnome/gnome-svn-2008-1.xml"]
	#xml_logs = ["logs/4_0_1-branch.xml", "logs/4_0_2-branch.xml", "logs/4_0_3-branch.xml", ]
	#xml_logs = ["logs/post-4_0-trunk.xml"]
	#xml_logs = ["logs/digital-clock.xml"]
	#xml_logs = []
	#for d in os.listdir("logs/gnome/"):
	#	xml_logs.append("logs/gnome/"+d)
	#print xml_logs
	#sys.exit()
	
	#projects = [
	#	"/home/sebas/work/projecten-codeyard/OPS",
	#	"/home/sebas/work/projecten-codeyard/DigiSuper" ]
	if svn_up:
		for p in projects:
			print "Updating " + p
			os.system("svn up %s > /dev/null" % p )
	
	#for p in projects:
	for p in xml_logs:
		title = p.split("/")[-1].split(".")[0].replace("-", " ").replace("_", ".")
		print "Creating image of %s %s " % (title, p)
		svnim = SvnChart(p, title)
		svnim.setBgImage("kde-logo.png")
		
		#svnim.filterUsers(["root","sebas","adridg","Spiertz1","jasper"])
		svnim.filterUsers(["scripty"])#,"root","Spiertz1"])
		svnim.createCsv()
		#svnim.createGraph()
