# GARstow portfile creation
# Copyright 2009 Adam Sampson <ats@offog.org>

from utils import *
import os, re

class PortLine:
	"""Abstract class for line specifications when creating or updating
	port files."""
	def create(self, f):
		"""Print a new line to the given file."""
		pass
	def match(self, line):
		"""Return whether this rule matches an existing line."""
		pass
	def update(self, line):
		"""Update an existing line that this rule matches, by
		returning a new line.
		(The line must include the trailing \\n, so you can delete the
		existing line by returning the empty string.)"""
		return line

class VarLine(PortLine):
	"""A variable line."""
	var_re = re.compile(r'^(\S+)\s*=\s*(.*)$')
	def __init__(self, key, value, mode = "ignore"):
		self.key = key
		self.value = value
		self.mode = mode
	def make_line(self, key, value):
		if value is None:
			return ""
		else:
			return (key + " = " + value).rstrip() + "\n"
	def create(self, f):
		f.write(self.make_line(self.key, self.value))
	def match(self, line):
		m = self.var_re.match(line)
		if m is None:
			return False
		elif m.group(1) != self.key:
			return False
		else:
			return True
	def update(self, line):
		m = self.var_re.match(line)
		oldvalue = m.group(2)

		if self.mode == "ignore":
			return line
		elif self.mode == "update":
			return self.make_line(self.key, self.value)
		elif self.mode == "merge-deps":
			def split(s):
				if s is None or s.strip() == "":
					return []
				else:
					return s.split()
			newdeps = set(split(self.value)).union(split(oldvalue))
			return self.make_line(self.key, " ".join(sorted(newdeps)))

class BlankLine(PortLine):
	"""A blank line."""
	def create(self, f):
		f.write("\n")
	def match(self, line):
		return line == "\n"

class IncludeLine(PortLine):
	"""An include line."""
	def __init__(self, file):
		self.line = "include " + file + "\n"
	def create(self, f):
		f.write(self.line)
	def match(self, line):
		return line == self.line

def update_portfile(fn, lines):
	"""Create or update a port Makefile using the given list of line
	specifications."""

	mkdir_p(os.path.dirname(fn))
	try:
		fin = open(fn)
	except IOError:
		fin = None

	newfn = "%s.new-%d" % (fn, os.getpid())
	fout = open(newfn, "w")

	# Insert any lines that we haven't found matches for.
	def skipped(start, end):
		for i in range(start, end):
			lines[i].create(fout)

	if fin is None:
		# Create a new portfile.
		skipped(0, len(lines))
	else:
		# Portfile already exists -- update it, keeping track of how
		# far we've got through what we think the file should look
		# like.
		pos = 0
		while True:
			oldl = fin.readline()
			if oldl == "":
				break

			# Join continuation lines together.
			while oldl.endswith("\\\n"):
				l = fin.readline()
				if l == "":
					break
				oldl += l
			clean_oldl = oldl.replace("\\\n", "")

			newl = oldl
			for i in range(pos, len(lines)):
				line = lines[i]
				if line.match(clean_oldl):
					# Insert any lines we should have seen
					# by now...
					skipped(pos, i)
					# Then update this line.
					newl = line.update(clean_oldl)
					pos = i + 1
					break
			fout.write(newl)

		skipped(pos, len(lines))

	fout.close()
	os.rename(newfn, fn)
