#
# Interactively decide what to distribute
#
#
# The exclude file signals files to always exclude,
# The pattern file lines are of the form
# *.c
# This excludes all files ending in .c.
#
# The include file signals files and directories to include.
# Records are of the form
# ('Tools:bgen:AE:AppleEvents.py', 'Lib:MacToolbox:AppleEvents.py')
# This includes the specified file, putting it in the given place, or
# ('Tools:bgen:AE:AppleEvents.py', None)
# This excludes the specified file.
#
from MkDistr_ui import *
import fnmatch
import regex
import os
import sys
import macfs
import macostools

SyntaxError='Include/exclude file syntax error'

class Matcher:
	"""Include/exclude database, common code"""
	
	def __init__(self, filename):
		self.filename = filename
		self.rawdata = []
		self.parse(filename)
		self.rawdata.sort()
		self.rebuild()
		self.modified = 0

	def parse(self, dbfile):
		try:
			fp = open(dbfile)
		except IOError:
			return
		data = fp.readlines()
		fp.close()
		for d in data:
			d = d[:-1]
			if not d or d[0] == '#': continue
			pat = self.parseline(d)
			self.rawdata.append(pat)
				
	def save(self):
		fp = open(self.filename, 'w')
		self.savedata(fp, self.rawdata)
		self.modified = 0
			
	def add(self, value):
		if len(value) == 1:
			value = value + ('',)
		self.rawdata.append(value)
		self.rebuild1(value)
		self.modified = 1
		
	def delete(self, value):
		key = value
		for i in range(len(self.rawdata)):
			if self.rawdata[i][0] == key:
				del self.rawdata[i]
				self.unrebuild1(i, key)
				self.modified = 1
				return
		print 'Not found!', key
				
	def getall(self):
		return map(lambda x: x[0], self.rawdata)
	
	def get(self, value):
		for src, dst in self.rawdata:
			if src == value:
				return src, dst
		print 'Not found!', value
				
	def is_modified(self):
		return self.modified
							
class IncMatcher(Matcher):
	"""Include filename database and matching engine"""

	def rebuild(self):
		self.idict = {}
		self.edict = {}
		for v in self.rawdata:
			self.rebuild1(v)
			
	def parseline(self, line):
		try:
			data = eval(line)
		except:
			raise SyntaxError, line
		if type(data) <> type(()) or len(data) not in (1,2):
			raise SyntaxError, line
		if len(data) == 1:
			data = data + ('',)
		return data
		
	def savedata(self, fp, data):
		for d in self.rawdata:
			fp.write(`d`+'\n')
		
	def rebuild1(self, (src, dst)):
		if dst == '':
			dst = src
		if dst == None:
			self.edict[src] = None
		else:
			self.idict[src] = dst
			
	def unrebuild1(self, num, src):
		if self.idict.has_key(src):
			del self.idict[src]
		else:
			del self.edict[src]
	
	def match(self, patharg):
		removed = []
		# First check the include directory
		path = patharg
		while 1:
			if self.idict.has_key(path):
				# We know of this path (or initial piece of path)
				dstpath = self.idict[path]
				# We do want it distributed. Tack on the tail.
				while removed:
					dstpath = os.path.join(dstpath, removed[0])
					removed = removed[1:]
				# Finally, if the resultant string ends in a separator
				# tack on our input filename
				if dstpath[-1] == os.sep:
					dir, file = os.path.split(path)
					dstpath = os.path.join(dstpath, file)
				return dstpath
			path, lastcomp = os.path.split(path)
			if not path:
				break
			removed[0:0] = [lastcomp]
		# Next check the exclude directory
		path = patharg
		while 1:
			if self.edict.has_key(path):
				return ''
			path, lastcomp = os.path.split(path)
			if not path:
				break
			removed[0:0] = [lastcomp]
		return None
			
	def checksourcetree(self):
		rv = []
		for name in self.idict.keys():
			if not os.path.exists(name):
				rv.append(name)
		return rv
				
class ExcMatcher(Matcher):
	"""Exclude pattern database and matching engine"""

	def rebuild(self):
		self.relist = []
		for v in self.rawdata:
			self.rebuild1(v)
		
	def parseline(self, data):
		return (data, None)

	def savedata(self, fp, data):
		for d in self.rawdata:
			fp.write(d[0]+'\n')		
		
	def rebuild1(self, (src, dst)):
		pat = fnmatch.translate(src)
		self.relist.append(regex.compile(pat))
		
	def unrebuild1(self, num, src):
		del self.relist[num]
	
	def match(self, path):
		comps = os.path.split(path)
		file = comps[-1]
		for pat in self.relist:
			if pat and pat.match(file) == len(file):
				return 1
		return 0		
		 
		
class Main:
	"""The main program glueing it all together"""
	
	def __init__(self):
		InitUI()
		fss, ok = macfs.GetDirectory('Source directory:')
		if not ok:
			sys.exit(0)
		os.chdir(fss.as_pathname())
		if not os.path.isdir(':(MkDistr)'):
			os.mkdir(':(MkDistr)')
		typedist = GetType()
		self.inc = IncMatcher(':(MkDistr):%s.include'%typedist)
		self.exc = ExcMatcher(':(MkDistr):%s.exclude'%typedist)
		self.ui = MkDistrUI(self)
		self.ui.mainloop()
		
	def check(self):
		return self.checkdir(':', 1)
		
	def checkdir(self, path, istop):
		files = os.listdir(path)
		rv = []
		todo = []
		for f in files:
			if self.exc.match(f):
				continue
			fullname = os.path.join(path, f)
			if self.inc.match(fullname) == None:
				if os.path.isdir(fullname):
					todo.append(fullname)
				else:
					rv.append(fullname)
		for d in todo:
			if len(rv) > 100:
				if istop:
					rv.append('... and more ...')
				return rv
			rv = rv + self.checkdir(d, 0)
		return rv
		
	def run(self, destprefix):
		missing = self.inc.checksourcetree()
		if missing:
			print '==== Missing source files ===='
			for i in missing:
				print i
			print '==== Fix and retry ===='
			return
		if not self.rundir(':', destprefix, 0):
			return
		self.rundir(':', destprefix, 1)

	def rundir(self, path, destprefix, doit):
		files = os.listdir(path)
		todo = []
		rv = 1
		for f in files:
			if self.exc.match(f):
				continue
			fullname = os.path.join(path, f)
			if os.path.isdir(fullname):
				todo.append(fullname)
			else:
				dest = self.inc.match(fullname)
				if dest == None:
					print 'Not yet resolved:', fullname
					rv = 0
				if dest:
					if doit:
						print 'COPY ', fullname
						print '  -> ', os.path.join(destprefix, dest)
						macostools.copy(fullname, os.path.join(destprefix, dest), 1)
		for d in todo:
			if not self.rundir(d, destprefix, doit):
				rv = 0
		return rv
		
	def save(self):
		self.inc.save()
		self.exc.save()
		
	def is_modified(self):
		return self.inc.is_modified() or self.exc.is_modified()

if __name__ == '__main__':
	Main()
	
