The 0.5 release happened on 2/15, not on 2/14. :-)
[python/dscho.git] / Mac / scripts / MkDistr.py
bloba9c79374d4739cd6e90b6d0be2efe06397d18ac5
2 # Interactively decide what to distribute
5 # The exclude file signals files to always exclude,
6 # The pattern file lines are of the form
7 # *.c
8 # This excludes all files ending in .c.
10 # The include file signals files and directories to include.
11 # Records are of the form
12 # ('Tools:bgen:AE:AppleEvents.py', 'Lib:MacToolbox:AppleEvents.py')
13 # This includes the specified file, putting it in the given place, or
14 # ('Tools:bgen:AE:AppleEvents.py', None)
15 # This excludes the specified file.
17 from MkDistr_ui import *
18 import fnmatch
19 import regex
20 import os
21 import sys
22 import macfs
23 import macostools
25 DEBUG=0
27 SyntaxError='Include/exclude file syntax error'
29 class Matcher:
30 """Include/exclude database, common code"""
32 def __init__(self, filename):
33 self.filename = filename
34 self.rawdata = []
35 self.parse(filename)
36 self.rawdata.sort()
37 self.rebuild()
38 self.modified = 0
40 def parse(self, dbfile):
41 try:
42 fp = open(dbfile)
43 except IOError:
44 return
45 data = fp.readlines()
46 fp.close()
47 for d in data:
48 d = d[:-1]
49 if not d or d[0] == '#': continue
50 pat = self.parseline(d)
51 self.rawdata.append(pat)
53 def save(self):
54 fp = open(self.filename, 'w')
55 self.savedata(fp, self.rawdata)
56 self.modified = 0
58 def add(self, value):
59 if len(value) == 1:
60 value = value + ('',)
61 self.rawdata.append(value)
62 self.rebuild1(value)
63 self.modified = 1
65 def delete(self, value):
66 key = value
67 for i in range(len(self.rawdata)):
68 if self.rawdata[i][0] == key:
69 del self.rawdata[i]
70 self.unrebuild1(i, key)
71 self.modified = 1
72 return
73 print 'Not found!', key
75 def getall(self):
76 return map(lambda x: x[0], self.rawdata)
78 def get(self, value):
79 for src, dst in self.rawdata:
80 if src == value:
81 return src, dst
82 print 'Not found!', value
84 def is_modified(self):
85 return self.modified
87 class IncMatcher(Matcher):
88 """Include filename database and matching engine"""
90 def rebuild(self):
91 self.idict = {}
92 self.edict = {}
93 for v in self.rawdata:
94 self.rebuild1(v)
96 def parseline(self, line):
97 try:
98 data = eval(line)
99 except:
100 raise SyntaxError, line
101 if type(data) <> type(()) or len(data) not in (1,2):
102 raise SyntaxError, line
103 if len(data) == 1:
104 data = data + ('',)
105 return data
107 def savedata(self, fp, data):
108 for d in self.rawdata:
109 fp.write(`d`+'\n')
111 def rebuild1(self, (src, dst)):
112 if dst == '':
113 dst = src
114 if dst == None:
115 self.edict[src] = None
116 else:
117 self.idict[src] = dst
119 def unrebuild1(self, num, src):
120 if self.idict.has_key(src):
121 del self.idict[src]
122 else:
123 del self.edict[src]
125 def match(self, patharg):
126 removed = []
127 # First check the include directory
128 path = patharg
129 while 1:
130 if self.idict.has_key(path):
131 # We know of this path (or initial piece of path)
132 dstpath = self.idict[path]
133 # We do want it distributed. Tack on the tail.
134 while removed:
135 dstpath = os.path.join(dstpath, removed[0])
136 removed = removed[1:]
137 # Finally, if the resultant string ends in a separator
138 # tack on our input filename
139 if dstpath[-1] == os.sep:
140 dir, file = os.path.split(path)
141 dstpath = os.path.join(dstpath, file)
142 if DEBUG:
143 print 'include', patharg, dstpath
144 return dstpath
145 path, lastcomp = os.path.split(path)
146 if not path:
147 break
148 removed[0:0] = [lastcomp]
149 # Next check the exclude directory
150 path = patharg
151 while 1:
152 if self.edict.has_key(path):
153 if DEBUG:
154 print 'exclude', patharg, path
155 return ''
156 path, lastcomp = os.path.split(path)
157 if not path:
158 break
159 removed[0:0] = [lastcomp]
160 if DEBUG:
161 print 'nomatch', patharg
162 return None
164 def checksourcetree(self):
165 rv = []
166 for name in self.idict.keys():
167 if not os.path.exists(name):
168 rv.append(name)
169 return rv
171 class ExcMatcher(Matcher):
172 """Exclude pattern database and matching engine"""
174 def rebuild(self):
175 self.relist = []
176 for v in self.rawdata:
177 self.rebuild1(v)
179 def parseline(self, data):
180 return (data, None)
182 def savedata(self, fp, data):
183 for d in self.rawdata:
184 fp.write(d[0]+'\n')
186 def rebuild1(self, (src, dst)):
187 pat = fnmatch.translate(src)
188 if DEBUG:
189 print 'PATTERN', `src`, 'REGEX', `pat`
190 self.relist.append(regex.compile(pat))
192 def unrebuild1(self, num, src):
193 del self.relist[num]
195 def match(self, path):
196 comps = os.path.split(path)
197 file = comps[-1]
198 for pat in self.relist:
199 if pat and pat.match(file) == len(file):
200 if DEBUG:
201 print 'excmatch', file, pat
202 return 1
203 return 0
206 class Main:
207 """The main program glueing it all together"""
209 def __init__(self):
210 InitUI()
211 fss, ok = macfs.GetDirectory('Source directory:')
212 if not ok:
213 sys.exit(0)
214 os.chdir(fss.as_pathname())
215 if not os.path.isdir(':Mac:Distributions'):
216 os.mkdir(':Mac:Distributions')
217 typedist = GetType()
218 self.inc = IncMatcher(':Mac:Distributions:%s.include'%typedist)
219 self.exc = ExcMatcher(':Mac:Distributions:%s.exclude'%typedist)
220 self.ui = MkDistrUI(self)
221 self.ui.mainloop()
223 def check(self):
224 return self.checkdir(':', 1)
226 def checkdir(self, path, istop):
227 if DEBUG:
228 print 'checkdir', path
229 files = os.listdir(path)
230 rv = []
231 todo = []
232 for f in files:
233 if DEBUG:
234 print 'checkfile', f
235 if self.exc.match(f):
236 if DEBUG:
237 print 'exclude match', f
238 continue
239 fullname = os.path.join(path, f)
240 if DEBUG:
241 print 'checkpath', fullname
242 matchvalue = self.inc.match(fullname)
243 if matchvalue == None:
244 if os.path.isdir(fullname):
245 if DEBUG:
246 print 'do dir', fullname
247 todo.append(fullname)
248 else:
249 if DEBUG:
250 print 'include', fullname
251 rv.append(fullname)
252 elif DEBUG:
253 print 'badmatch', matchvalue
254 for d in todo:
255 if len(rv) > 500:
256 if istop:
257 rv.append('... and more ...')
258 return rv
259 rv = rv + self.checkdir(d, 0)
260 return rv
262 def run(self, destprefix):
263 missing = self.inc.checksourcetree()
264 if missing:
265 print '==== Missing source files ===='
266 for i in missing:
267 print i
268 print '==== Fix and retry ===='
269 return
270 if not self.rundir(':', destprefix, 0):
271 return
272 self.rundir(':', destprefix, 1)
274 def rundir(self, path, destprefix, doit):
275 files = os.listdir(path)
276 todo = []
277 rv = 1
278 for f in files:
279 if self.exc.match(f):
280 continue
281 fullname = os.path.join(path, f)
282 if os.path.isdir(fullname):
283 todo.append(fullname)
284 else:
285 dest = self.inc.match(fullname)
286 if dest == None:
287 print 'Not yet resolved:', fullname
288 rv = 0
289 if dest:
290 if doit:
291 print 'COPY ', fullname
292 print ' -> ', os.path.join(destprefix, dest)
293 try:
294 macostools.copy(fullname, os.path.join(destprefix, dest), 1)
295 except: #DBG
296 print 'cwd', os.getcwd() #DBG
297 print 'fsspec', macfs.FSSpec(fullname) #DBG
298 raise
299 for d in todo:
300 if not self.rundir(d, destprefix, doit):
301 rv = 0
302 return rv
304 def save(self):
305 self.inc.save()
306 self.exc.save()
308 def is_modified(self):
309 return self.inc.is_modified() or self.exc.is_modified()
311 if __name__ == '__main__':
312 Main()