User:WindPower/PrettierDiff.py

From Team Fortress Wiki
Jump to: navigation, search

This thing is not on autopilot, son. It uses output from diff in order to generate a pwetty diff.

# -*- coding: utf-8 -*-

import re
import wikitools

fileRe = re.compile(r'^Index:\s*([^\r\n]*)\s+={10,}[\r\n]+((?:(?!Index:).*[\r\n]+)+)', re.MULTILINE)
lineMatch = re.compile(r'^@@\s*-(\d+),\d+\s*\+(\d+),(\d+)\s*@@$')

def u(s):
	if type(s) is type(u''):
		return s
	if type(s) is type(''):
		try:
			return unicode(s)
		except:
			try:
				return unicode(s.decode('utf8'))
			except:
				try:
					return unicode(s.decode('windows-1252'))
				except:
					return unicode(s, errors='ignore')
	try:
		return unicode(s)
	except:
		try:
			return u(str(s))
		except:
			return s

def pootDiff(wiki, patchName, diffdata):
	diffdata = u(diffdata)
	patchName = u(patchName)
	files = []
	for r in fileRe.finditer(diffdata):
		f = u(r.group(1))
		contents = u(r.group(2)).strip()
		isBinary = contents.find('svn:mime-type = application/octet-stream') != -1
		files.append({
			'name': f,
			'contents': contents,
			'isBinary': isBinary
		})
	def cmpFiles(left, right):
		c1 = cmp(left['isBinary'], right['isBinary'])
		if c1:
			return c1
		return cmp(left['name'], right['name'])
	files.sort(cmp=cmpFiles)
	def formatFile(f):
		isDelete = False
		isAdd = False
		isBinary = f['isBinary']
		contents = []
		lines = f['contents'].split(u'\n')
		def getLineContent(l):
			if not l.strip():
				return u' '
			return l.replace(u'\t', u'        ')
		diffOpen = False
		lineOld = -1
		lineNew = -1
		for l in lines:
			l = l.replace(u'\r', '').replace(u'\n', '')
			lineRes = lineMatch.search(l)
			if lineRes is not None:
				if not int(lineRes.group(2)) and not int(lineRes.group(3)):
					isDelete = True
					contents = []
					break
				if diffOpen:
					contents.append((u'\u2026'))
				diffOpen = True
				lineOld = int(lineRes.group(1))
				lineNew = int(lineRes.group(2))
			elif (l[:3] == u'---' and l.find(u'(revision 0)') != -1) or l == u'Added: svn:mime-type':
				isAdd = True
			elif l[:14] == u'Cannot display':
				isBinary = True
			elif diffOpen and len(l):
				if l[0] == u' ':
					contents.append((u' ', lineOld, lineNew, l[1:]))
					lineOld += 1
					lineNew += 1
				elif l[0] == u'+':
					contents.append((u'+', lineNew, l[1:]))
					lineNew += 1
				elif l[0] == u'-':
					contents.append((u'-', lineOld, l[1:]))
					lineOld += 1
		ret = u'<div class="diff-file'
		if isDelete:
			ret += u' diff-file-deleted'
		ret += u'"><div class="'
		if isBinary:
			ret += u'diff-name-binary'
		else:
			ret += u'diff-name-text'
		ret += u'">'
		if isAdd:
			ret += u'Added'
		elif isDelete:
			ret += u'Deleted'
		else:
			ret += u'Modified'
		ret += u': <span class="diff-name">'
		subPageName = u'Template:PatchDiff/' + patchName + u'/' + f['name']
		if not isBinary:
			ret += u'[[' + subPageName + u'|' + f['name'] + u']]'
		else:
			ret += f['name']
		ret += u'</span></div>'
		if len(contents):
			ret += u'<div class="diff-contents"></div>'
			diffRet = []
			lastBlock = None
			for c in contents:
				if not len(c):
					continue
				d = u'<div class="diff-line-entry'
				if c[0] == u' ':
					d += u' diff-line-nochange'
				elif c[0] == u'+':
					d += u' diff-line-add'
				elif c[0] == u'-':
					d += u' diff-line-remove'
				else: # Ellipsis
					diffRet.append(u'<div class="diff-line-ellipsis">\u2026</div>')
					lastBlock = None
					classIndex = diffRet[-1].find(u'">')
					if classIndex != -1:
						diffRet[-1] = diffRet[-1][:classIndex] + u' diff-line-last' + diffRet[-1][classIndex:]
					continue
				if c[0] != lastBlock:
					d += u' diff-line-first'
					if lastBlock is not None:
						classIndex = diffRet[-1].find(u'">')
						if classIndex != -1:
							diffRet[-1] = diffRet[-1][:classIndex] + u' diff-line-last' + diffRet[-1][classIndex:]
				lastBlock = c[0]
				d += u'">'
				if c[0] == u' ':
					d += u'<span class="diff-line-old">' + u(c[1]) + u'</span><span class="diff-line-new">' + u(c[2]) + u'</span>'
				elif c[0] == u'+':
					d += u'<span class="diff-line-old diff-line-na">N/A</span><span class="diff-line-new">' + u(c[1]) + u'</span>'
				elif c[0] == u'-':
					d += u'<span class="diff-line-old">' + u(c[1]) + u'</span><span class="diff-line-new diff-line-na">N/A</span>'
				d += u'<span class="diff-line">' + getLineContent(c[-1]) + u'</span></div>'
				diffRet.append(d)
			finalText = u(u''.join(diffRet))
			wikitools.page.Page(wiki, subPageName).edit(finalText, summary=u'Diff of file "' + f['name'] + u'" for patch [[:' + patchName + u']].', minor=True, bot=True, skipmd5=True)
		ret += u'</div>'
		return ret
	patchDiff = u''
	for f in files:
		print 'Processing file:', f['name'], 'in patch:', patchName
		d = formatFile(f)
		patchDiff += d
		print d
	print 'Editing patch diff page:', u'Template:PatchDiff/' + patchName
	wikitools.page.Page(wiki, u'Template:PatchDiff/' + patchName).edit(patchDiff, summary=u'Diff of patch [[:' + patchName + u']].', minor=True, bot=True)

def poot(wikiApi, wikiUsername, wikiPassword, patchName, diffFile):
	wiki = wikitools.wiki.Wiki(wikiApi)
	if wiki.login(wikiUsername, wikiPassword):
		return pootDiff(wiki, patchName, open(diffFile, 'rb').read(-1))
	else:
		print 'Invalid wiki username/password.'

if __name__ == '__main__':
	wikiApi = raw_input('Poot Wiki API URL: ')
	wikiUsername = raw_input('Poot Wiki username: ')
	wikiPassword = raw_input('Poot Wiki password: ')
	patchName = raw_input('Poot Wiki patch page title: ')
	diffFile = raw_input('Poot path of file where diff outpoot is saved: ')
	print 'Pooting...'
	poot(wikiApi, wikiUsername, wikiPassword, patchName, diffFile)
	print 'Is gud.'