| # |
| # 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 re |
| import os |
| import sys |
| import macfs |
| import macostools |
| |
| DEBUG=0 |
| |
| 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) |
| if DEBUG: |
| print 'include', patharg, dstpath |
| 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): |
| if DEBUG: |
| print 'exclude', patharg, path |
| return '' |
| path, lastcomp = os.path.split(path) |
| if not path: |
| break |
| removed[0:0] = [lastcomp] |
| if DEBUG: |
| print 'nomatch', patharg |
| 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) |
| if DEBUG: |
| print 'PATTERN', `src`, 'REGEX', `pat` |
| self.relist.append(re.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): |
| if DEBUG: |
| print 'excmatch', file, pat |
| return 1 |
| return 0 |
| |
| |
| class Main: |
| """The main program glueing it all together""" |
| |
| def __init__(self): |
| InitUI() |
| os.chdir(sys.prefix) |
| if not os.path.isdir(':Mac:Distributions'): |
| os.mkdir(':Mac:Distributions') |
| self.typedist = GetType() |
| self.inc = IncMatcher(':Mac:Distributions:%s.include'%self.typedist) |
| self.exc = ExcMatcher(':Mac:Distributions:%s.exclude'%self.typedist) |
| self.ui = MkDistrUI(self) |
| self.ui.mainloop() |
| |
| def check(self): |
| return self.checkdir(':', 1) |
| |
| def checkdir(self, path, istop): |
| if DEBUG: |
| print 'checkdir', path |
| files = os.listdir(path) |
| rv = [] |
| todo = [] |
| for f in files: |
| if DEBUG: |
| print 'checkfile', f |
| if self.exc.match(f): |
| if DEBUG: |
| print 'exclude match', f |
| continue |
| fullname = os.path.join(path, f) |
| if DEBUG: |
| print 'checkpath', fullname |
| matchvalue = self.inc.match(fullname) |
| if matchvalue == None: |
| if os.path.isdir(fullname): |
| if DEBUG: |
| print 'do dir', fullname |
| todo.append(fullname) |
| else: |
| if DEBUG: |
| print 'include', fullname |
| rv.append(fullname) |
| elif DEBUG: |
| print 'badmatch', matchvalue |
| for d in todo: |
| if len(rv) > 500: |
| if istop: |
| rv.append('... and more ...') |
| return rv |
| rv = rv + self.checkdir(d, 0) |
| return rv |
| |
| def run(self): |
| missing = self.inc.checksourcetree() |
| if missing: |
| print '==== Missing source files ====' |
| for i in missing: |
| print i |
| print '==== Fix and retry ====' |
| return |
| destprefix = os.path.join(sys.prefix, ':Mac:Distributions:(vise)') |
| destprefix = os.path.join(destprefix, '%s Distribution'%self.typedist) |
| 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) |
| try: |
| macostools.copy(fullname, os.path.join(destprefix, dest), 1) |
| except: #DBG |
| print '*** Copy failed mysteriously, try again' |
| print '*** cwd', os.getcwd() #DBG |
| print '*** fsspec', macfs.FSSpec(fullname) #DBG |
| # Get rid of open files |
| try: |
| i = 1 / 0 |
| except: |
| pass |
| 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() |
| |