#!/usr/bin/env python
"""
xen-port-patches.py [git dir]
Create custom patches for xen from custom patches that affect x86.
Turned out to be a little functional programming exercise for the author.
(c) 2005-04-06, Kurt Garloff <garloff@suse.de>
"""
__revision__ = '$Id'

import re, glob, sys, os, filecmp

# List of replacement rules
replrules = [(r"(.*)-xen(\.[^/\s])", r"\1\2"),
	   (r"include/asm-([^/\s]+86[^/\s]*)/mach-xen/asm/", r"include/asm-\1/"),
	   (r"arch/([^/\s]+86[^/\s]*)/include/mach-xen/asm/", r"arch/\1/include/asm/")]
# List of compiled rules (speed reasons)
#comprules = map(lambda(x): (x[0], x[1], re.compile(x[0])), replrules)
comprules = [ (x[0], x[1], re.compile(x[0])) for x in replrules ]

def applCorrFwd(path):
	"Try to apply replrules, return void elem if none applied, otherwise \
	 corresponding old and new name"
	# Maximal functional programming obfusc^W elegance
		# Drop element 2 from each tupel
	return map(lambda(x): (x[0], x[1]), \
		# Filter out non-matched path combinations
		filter(lambda(x): x[2], \
		# Try all replacement rules
		map(lambda(x): (path, 
			re.sub(x[0], x[1], path), x[2].search(path)),
			comprules)))


def createReplList(patch):
	"Creates a list of files to watch and their corresp xen file names"
	pfile = open(patch, "r")
	srch = re.compile(r"^\+\+\+ [^/\s]+/([\S]*)")
	# Again illegi^W beautiful functional programming
	# return matched string no 1
	matches = map(lambda(m): m.group(1), \
		# filter out non-matches
		filter(lambda(m): m, \
		# find matches
		map(lambda(line): srch.search(line), pfile)))
	pfile.close()
	#print matches
	# Try path replacements, drop empty elements and one nesting level
	return map(lambda(x): (x[0][0], x[0][1]), \
			filter(lambda(x): x, map(applCorrFwd, matches)))

def findPatchFiles(kgit):
	"Returns a list of all files in patches.*/ on below kgit"
	list = glob.glob(kgit + "/patches.*/*")
	# Avoid patches in patches.xen, backup files
	filterout = re.compile("(patches\.xen|~$|\.#)")
	return filter(lambda(x): not filterout.search(x), list)

def writePatch(fname, hdr, body):
	"Create xen patch corresponding to other patch"
	xenrepl = re.compile(r"^.*\/([^\/]*)$")
	xenfname = re.sub(xenrepl, r"xen3-\1", fname)
	shortrepl = re.compile(r"^.*\/([^\/]*\/[^\/]*)$")
	shortname = re.sub(shortrepl, r"\1", fname)
	origname = ".".join([xenfname, "orig"])
	print "%s -> %s" % (shortname, xenfname)
	if os.access(xenfname, os.F_OK):
		os.rename(xenfname, origname)
	pfile = open(xenfname, "w")
	pfile.write(hdr)
	pfile.write("Automatically created from \"%s\" by " \
			"xen-port-patches.py\n\n" % shortname)
	pfile.write(body)
	pfile.close()
	if os.access(origname, os.F_OK):
		if filecmp.cmp(xenfname, origname):
			os.remove(xenfname)
	else:
		ser = open("xen3-series.conf", "a")
		ser.write("\t\t%s\n" % xenfname)
		ser.close()

def mayCreatePatch(fname, repls):
	"Try to apply the replacement rules to fname"
	if fname.endswith(".gz"):
		decomp ="gzip"
	elif fname.endswith(".bz2"):
		decomp = "bzip2"
	elif fname.endswith(".xz"):
		decomp = "xz"
	else:
		decomp = ""
	if decomp == "":
		pfile = open(fname, "r")
	else:
		pfile = os.popen(" ".join([decomp, "-dc", fname]), "r")
		parts = fname.split(".")
		del parts[-1]
		fname = ".".join(parts)
	# Again an unelegant loop with an ugly state machine
	active = 0; header = 0
	patch = ""; rule = ()
	patchheader = ""; pheaderactive = 1
	endmarker = re.compile(r"^(Index|diff|CVS|RCS|\-\-\-|\+\+\+|===)")
	for line in pfile:
		hmark = endmarker.search(line)
		if pheaderactive:
			if hmark:
				pheaderactive = 0
			else:
				patchheader += line
				continue
		# If we get here, we're past the patch file header
		if active:
			if header:
				if not hmark:
					header = 0
				#else:
				patch += re.sub(rule[1], rule[0], line)
			else:
				if hmark:
					active = 0
				else:
					patch += line
		# else is no good, need to test again
		if not active and hmark:
			matches = filter(lambda(x): x[2],
				map(lambda(x): (x[0], x[1], x[2].search(line)), repls))
			if matches:
				active = 1; header = 1
				# There should never be more than one match ...
				rule = (matches[0][0], matches[0][1])
				patch += re.sub(rule[1], rule[0], line)
	pfile.close()
	if patch:
		writePatch(fname, patchheader, patch)

def createXenPatches(filelist, repls):
	"For each file in the list, find hunks that may be needed for Xen"
	# We could do this again with functional programming, but the
	# memory requirements may be significant, so let's create a
	# loop on the per patchfile level.
	for pfile in filelist:
		mayCreatePatch(pfile, repls)

def main(args):
	"Main program"
	# Allow overriding kernel git dir
	if len(args) > 1 and args[1] != ".":
		kerngit = args[1]
	else:
		kerngit = re.sub(r"^(.*)/[^/]+/[^/]+$", r"\1", os.path.abspath(args[0]))
	print "Using kernel git at '%s'" % (kerngit)
	# Create list of replacements
	repllist = createReplList(kerngit + "/patches.xen/xen3-auto-xen-arch.diff")
	#print repllist
	# ... and compile
	complrepl = map(lambda(x): (x[0], x[1], re.compile(x[1])), repllist)
	# Create a list of patch files
	patchfiles = findPatchFiles(kerngit)
	#print patchfiles
	createXenPatches(patchfiles + args[2:], complrepl)

# Entry point
if __name__ == '__main__':
	main(sys.argv)

