Oops -- Lib/Test should be Lib/test, of course!
[python/dscho.git] / Mac / scripts / gensuitemodule.py
blob53da22c1cea424fe76391402738d955b35596901
1 """
2 gensuitemodule - Generate an AE suite module from an aete/aeut resource
4 Based on aete.py
5 """
7 import MacOS
8 import os
9 import string
10 import sys
11 import types
12 import StringIO
13 import macfs
15 from Res import *
17 def main():
18 fss, ok = macfs.PromptGetFile('Select file with aeut/aete resource:')
19 if not ok:
20 sys.exit(0)
21 process(fss.as_pathname())
23 def process(fullname):
24 """Process all resources in a single file"""
25 cur = CurResFile()
26 print fullname
27 rf = OpenRFPerm(fullname, 0, 1)
28 try:
29 UseResFile(rf)
30 resources = []
31 for i in range(Count1Resources('aete')):
32 res = Get1IndResource('aete', 1+i)
33 resources.append(res)
34 for i in range(Count1Resources('aeut')):
35 res = Get1IndResource('aeut', 1+i)
36 resources.append(res)
37 print "\nLISTING aete+aeut RESOURCES IN", `fullname`
38 for res in resources:
39 print "decoding", res.GetResInfo(), "..."
40 data = res.data
41 aete = decode(data)
42 # switch back (needed for dialogs in Python)
43 UseResFile(cur)
44 compileaete(aete, fullname)
45 UseResFile(rf)
46 finally:
47 if rf <> cur:
48 CloseResFile(rf)
49 UseResFile(cur)
51 def decode(data):
52 """Decode a resource into a python data structure"""
53 f = StringIO.StringIO(data)
54 aete = generic(getaete, f)
55 aete = simplify(aete)
56 processed = f.tell()
57 unprocessed = len(f.read())
58 total = f.tell()
59 if unprocessed:
60 sys.stderr.write("%d processed + %d unprocessed = %d total\n" %
61 (processed, unprocessed, total))
62 return aete
64 def simplify(item):
65 """Recursively replace singleton tuples by their constituent item"""
66 if type(item) is types.ListType:
67 return map(simplify, item)
68 elif type(item) == types.TupleType and len(item) == 2:
69 return simplify(item[1])
70 else:
71 return item
74 # Here follows the aete resource decoder.
75 # It is presented bottom-up instead of top-down because there are direct
76 # references to the lower-level part-decoders from the high-level part-decoders.
78 def getbyte(f, *args):
79 c = f.read(1)
80 if not c:
81 raise EOFError, 'in getbyte' + str(args)
82 return ord(c)
84 def getword(f, *args):
85 getalign(f)
86 s = f.read(2)
87 if len(s) < 2:
88 raise EOFError, 'in getword' + str(args)
89 return (ord(s[0])<<8) | ord(s[1])
91 def getlong(f, *args):
92 getalign(f)
93 s = f.read(4)
94 if len(s) < 4:
95 raise EOFError, 'in getlong' + str(args)
96 return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3])
98 def getostype(f, *args):
99 getalign(f)
100 s = f.read(4)
101 if len(s) < 4:
102 raise EOFError, 'in getostype' + str(args)
103 return s
105 def getpstr(f, *args):
106 c = f.read(1)
107 if len(c) < 1:
108 raise EOFError, 'in getpstr[1]' + str(args)
109 nbytes = ord(c)
110 if nbytes == 0: return ''
111 s = f.read(nbytes)
112 if len(s) < nbytes:
113 raise EOFError, 'in getpstr[2]' + str(args)
114 return s
116 def getalign(f):
117 if f.tell() & 1:
118 c = f.read(1)
119 ##if c <> '\0':
120 ## print 'align:', `c`
122 def getlist(f, description, getitem):
123 count = getword(f)
124 list = []
125 for i in range(count):
126 list.append(generic(getitem, f))
127 getalign(f)
128 return list
130 def alt_generic(what, f, *args):
131 print "generic", `what`, args
132 res = vageneric(what, f, args)
133 print '->', `res`
134 return res
136 def generic(what, f, *args):
137 if type(what) == types.FunctionType:
138 return apply(what, (f,) + args)
139 if type(what) == types.ListType:
140 record = []
141 for thing in what:
142 item = apply(generic, thing[:1] + (f,) + thing[1:])
143 record.append((thing[1], item))
144 return record
145 return "BAD GENERIC ARGS: %s" % `what`
147 getdata = [
148 (getostype, "type"),
149 (getpstr, "description"),
150 (getword, "flags")
152 getargument = [
153 (getpstr, "name"),
154 (getostype, "keyword"),
155 (getdata, "what")
157 getevent = [
158 (getpstr, "name"),
159 (getpstr, "description"),
160 (getostype, "suite code"),
161 (getostype, "event code"),
162 (getdata, "returns"),
163 (getdata, "accepts"),
164 (getlist, "optional arguments", getargument)
166 getproperty = [
167 (getpstr, "name"),
168 (getostype, "code"),
169 (getdata, "what")
171 getelement = [
172 (getostype, "type"),
173 (getlist, "keyform", getostype)
175 getclass = [
176 (getpstr, "name"),
177 (getostype, "class code"),
178 (getpstr, "description"),
179 (getlist, "properties", getproperty),
180 (getlist, "elements", getelement)
182 getcomparison = [
183 (getpstr, "operator name"),
184 (getostype, "operator ID"),
185 (getpstr, "operator comment"),
187 getenumerator = [
188 (getpstr, "enumerator name"),
189 (getostype, "enumerator ID"),
190 (getpstr, "enumerator comment")
192 getenumeration = [
193 (getostype, "enumeration ID"),
194 (getlist, "enumerator", getenumerator)
196 getsuite = [
197 (getpstr, "suite name"),
198 (getpstr, "suite description"),
199 (getostype, "suite ID"),
200 (getword, "suite level"),
201 (getword, "suite version"),
202 (getlist, "events", getevent),
203 (getlist, "classes", getclass),
204 (getlist, "comparisons", getcomparison),
205 (getlist, "enumerations", getenumeration)
207 getaete = [
208 (getword, "major/minor version in BCD"),
209 (getword, "language code"),
210 (getword, "script code"),
211 (getlist, "suites", getsuite)
214 def compileaete(aete, fname):
215 """Generate code for a full aete resource. fname passed for doc purposes"""
216 [version, language, script, suites] = aete
217 major, minor = divmod(version, 256)
218 for suite in suites:
219 compilesuite(suite, major, minor, language, script, fname)
221 def compilesuite(suite, major, minor, language, script, fname):
222 """Generate code for a single suite"""
223 [name, desc, code, level, version, events, classes, comps, enums] = suite
225 modname = identify(name)
226 fss, ok = macfs.StandardPutFile('Python output file', modname+'.py')
227 if not ok:
228 return
229 fp = open(fss.as_pathname(), 'w')
230 fss.SetCreatorType('Pyth', 'TEXT')
232 fp.write('"""Suite %s: %s\n' % (name, desc))
233 fp.write("Level %d, version %d\n\n" % (level, version))
234 fp.write("Generated from %s\n"%fname)
235 fp.write("AETE/AEUT resource version %d/%d, language %d, script %d\n" % \
236 (major, minor, language, script))
237 fp.write('"""\n\n')
239 fp.write('import aetools\n')
240 fp.write('import MacOS\n\n')
241 fp.write("_code = %s\n\n"% `code`)
243 compileclassheader(fp, modname)
245 enumsneeded = {}
246 if events:
247 for event in events:
248 compileevent(fp, event, enumsneeded)
249 else:
250 fp.write("\tpass\n\n")
252 objc = ObjectCompiler(fp)
253 for cls in classes:
254 objc.compileclass(cls)
255 for cls in classes:
256 objc.fillclasspropsandelems(cls)
257 for comp in comps:
258 objc.compilecomparison(comp)
259 for enum in enums:
260 objc.compileenumeration(enum)
262 for enum in enumsneeded.keys():
263 objc.checkforenum(enum)
265 objc.dumpindex()
267 def compileclassheader(fp, name):
268 """Generate class boilerplate"""
269 fp.write("class %s:\n\n"%name)
271 def compileevent(fp, event, enumsneeded):
272 """Generate code for a single event"""
273 [name, desc, code, subcode, returns, accepts, arguments] = event
274 funcname = identify(name)
276 # generate name->keyword map
278 if arguments:
279 fp.write("\t_argmap_%s = {\n"%funcname)
280 for a in arguments:
281 fp.write("\t\t%s : %s,\n"%(`identify(a[0])`, `a[1]`))
282 fp.write("\t}\n\n")
285 # Generate function header
287 has_arg = (not is_null(accepts))
288 opt_arg = (has_arg and is_optional(accepts))
290 fp.write("\tdef %s(self, "%funcname)
291 if has_arg:
292 if not opt_arg:
293 fp.write("_object, ") # Include direct object, if it has one
294 else:
295 fp.write("_object=None, ") # Also include if it is optional
296 else:
297 fp.write("_no_object=None, ") # For argument checking
298 fp.write("_attributes={}, **_arguments):\n") # include attribute dict and args
300 # Generate doc string (important, since it may be the only
301 # available documentation, due to our name-remaping)
303 fp.write('\t\t"""%s: %s\n'%(name, desc))
304 if has_arg:
305 fp.write("\t\tRequired argument: %s\n"%getdatadoc(accepts))
306 elif opt_arg:
307 fp.write("\t\tOptional argument: %s\n"%getdatadoc(accepts))
308 for arg in arguments:
309 fp.write("\t\tKeyword argument %s: %s\n"%(identify(arg[0]),
310 getdatadoc(arg[2])))
311 fp.write("\t\tKeyword argument _attributes: AppleEvent attribute dictionary\n")
312 if not is_null(returns):
313 fp.write("\t\tReturns: %s\n"%getdatadoc(returns))
314 fp.write('\t\t"""\n')
316 # Fiddle the args so everything ends up in 'arguments' dictionary
318 fp.write("\t\t_code = %s\n"% `code`)
319 fp.write("\t\t_subcode = %s\n\n"% `subcode`)
321 # Do keyword name substitution
323 if arguments:
324 fp.write("\t\taetools.keysubst(_arguments, self._argmap_%s)\n"%funcname)
325 else:
326 fp.write("\t\tif _arguments: raise TypeError, 'No optional args expected'\n")
328 # Stuff required arg (if there is one) into arguments
330 if has_arg:
331 fp.write("\t\t_arguments['----'] = _object\n")
332 elif opt_arg:
333 fp.write("\t\tif _object:\n")
334 fp.write("\t\t\t_arguments['----'] = _object\n")
335 else:
336 fp.write("\t\tif _no_object != None: raise TypeError, 'No direct arg expected'\n")
337 fp.write("\n")
339 # Do enum-name substitution
341 for a in arguments:
342 if is_enum(a[2]):
343 kname = a[1]
344 ename = a[2][0]
345 if ename <> '****':
346 fp.write("\t\taetools.enumsubst(_arguments, %s, _Enum_%s)\n" %
347 (`kname`, ename))
348 enumsneeded[ename] = 1
349 fp.write("\n")
351 # Do the transaction
353 fp.write("\t\t_reply, _arguments, _attributes = self.send(_code, _subcode,\n")
354 fp.write("\t\t\t\t_arguments, _attributes)\n")
356 # Error handling
358 fp.write("\t\tif _arguments.has_key('errn'):\n")
359 fp.write("\t\t\traise aetools.Error, aetools.decodeerror(_arguments)\n")
360 fp.write("\t\t# XXXX Optionally decode result\n")
362 # Decode result
364 fp.write("\t\tif _arguments.has_key('----'):\n")
365 if is_enum(returns):
366 fp.write("\t\t\t# XXXX Should do enum remapping here...\n")
367 fp.write("\t\t\treturn _arguments['----']\n")
368 fp.write("\n")
370 # print "\n# Command %s -- %s (%s, %s)" % (`name`, `desc`, `code`, `subcode`)
371 # print "# returns", compiledata(returns)
372 # print "# accepts", compiledata(accepts)
373 # for arg in arguments:
374 # compileargument(arg)
376 def compileargument(arg):
377 [name, keyword, what] = arg
378 print "# %s (%s)" % (name, `keyword`), compiledata(what)
380 class ObjectCompiler:
381 def __init__(self, fp):
382 self.fp = fp
383 self.propnames = {}
384 self.classnames = {}
385 self.propcodes = {}
386 self.classcodes = {}
387 self.compcodes = {}
388 self.enumcodes = {}
389 self.othersuites = []
391 def findcodename(self, type, code):
392 while 1:
393 if type == 'property':
394 if self.propcodes.has_key(code):
395 return self.propcodes[code], self.propcodes[code], None
396 for s in self.othersuites:
397 if s._propdeclarations.has_key(code):
398 name = s._propdeclarations[code].__name__
399 return name, '%s.%s' % (s.__name__, name), s.__name__
400 if type == 'class':
401 if self.classcodes.has_key(code):
402 return self.classcodes[code], self.classcodes[code], None
403 for s in self.othersuites:
404 if s._classdeclarations.has_key(code):
405 name = s._classdeclarations[code].__name__
406 return name, '%s.%s' % (s.__name__, name), s.__name__
407 if type == 'enum':
408 if self.enumcodes.has_key(code):
409 return self.enumcodes[code], self.enumcodes[code], None
410 for s in self.othersuites:
411 if s._enumdeclarations.has_key(code):
412 name = '_Enum_' + identify(code)
413 return name, '%s.%s' % (s.__name__, name), s.__name__
414 if type == 'comparison':
415 if self.compcodes.has_key(code):
416 return self.compcodes[code], self.compcodes[code], None
417 for s in self.othersuites:
418 if s._compdeclarations.has_key(code):
419 name = s._compdeclarations[code].__name__
420 return name, '%s.%s' % (s.__name__, name), s.__name__
422 m = self.askdefinitionmodule(type, code)
423 if not m: return None, None, None
424 self.othersuites.append(m)
426 def askdefinitionmodule(self, type, code):
427 fss, ok = macfs.PromptGetFile('Where is %s %s declared?'%(type, code))
428 if not ok: return
429 path, file = os.path.split(fss.as_pathname())
430 modname = os.path.splitext(file)[0]
431 if not path in sys.path:
432 sys.path.insert(0, path)
433 m = __import__(modname)
434 self.fp.write("import %s\n"%modname)
435 return m
437 def compileclass(self, cls):
438 [name, code, desc, properties, elements] = cls
439 pname = identify(name)
440 if self.classcodes.has_key(code):
441 # plural forms and such
442 self.fp.write("\n%s = %s\n"%(pname, self.classcodes[code]))
443 self.classnames[pname] = code
444 else:
445 self.fp.write('\nclass %s(aetools.ComponentItem):\n' % pname)
446 self.fp.write('\t"""%s - %s"""\n' % (name, desc))
447 self.fp.write('\twant = %s\n' % `code`)
448 self.classnames[pname] = code
449 self.classcodes[code] = pname
450 for prop in properties:
451 self.compileproperty(prop)
452 for elem in elements:
453 self.compileelement(elem)
455 def compileproperty(self, prop):
456 [name, code, what] = prop
457 if code == 'c@#!':
458 # Something silly with plurals. Skip it.
459 return
460 pname = identify(name)
461 if self.propcodes.has_key(code):
462 self.fp.write("# repeated property %s %s\n"%(pname, what[1]))
463 else:
464 self.fp.write("class %s(aetools.NProperty):\n" % pname)
465 self.fp.write('\t"""%s - %s"""\n' % (name, what[1]))
466 self.fp.write("\twhich = %s\n" % `code`)
467 self.fp.write("\twant = %s\n" % `what[0]`)
468 self.propnames[pname] = code
469 self.propcodes[code] = pname
471 def compileelement(self, elem):
472 [code, keyform] = elem
473 self.fp.write("# element %s as %s\n" % (`code`, keyform))
475 def fillclasspropsandelems(self, cls):
476 [name, code, desc, properties, elements] = cls
477 cname = identify(name)
478 if self.classcodes[code] != cname:
479 # This is an other name (plural or so) for something else. Skip.
480 return
481 plist = []
482 elist = []
483 for prop in properties:
484 [pname, pcode, what] = prop
485 if pcode == 'c@#!':
486 continue
487 pname = identify(pname)
488 plist.append(pname)
489 for elem in elements:
490 [ecode, keyform] = elem
491 if ecode == 'c@#!':
492 continue
493 name, ename, module = self.findcodename('class', ecode)
494 if not name:
495 self.fp.write("# XXXX %s element %s not found!!\n"%(cname, `ecode`))
496 else:
497 elist.append(name, ename)
499 self.fp.write("%s._propdict = {\n"%cname)
500 for n in plist:
501 self.fp.write("\t'%s' : %s,\n"%(n, n))
502 self.fp.write("}\n")
503 self.fp.write("%s._elemdict = {\n"%cname)
504 for n, fulln in elist:
505 self.fp.write("\t'%s' : %s,\n"%(n, fulln))
506 self.fp.write("}\n")
508 def compilecomparison(self, comp):
509 [name, code, comment] = comp
510 iname = identify(name)
511 self.compcodes[code] = iname
512 self.fp.write("class %s(aetools.NComparison):\n" % iname)
513 self.fp.write('\t"""%s - %s"""\n' % (name, comment))
515 def compileenumeration(self, enum):
516 [code, items] = enum
517 name = "_Enum_%s" % identify(code)
518 self.fp.write("%s = {\n" % name)
519 for item in items:
520 self.compileenumerator(item)
521 self.fp.write("}\n\n")
522 self.enumcodes[code] = name
523 return code
525 def compileenumerator(self, item):
526 [name, code, desc] = item
527 self.fp.write("\t%s : %s,\t# %s\n" % (`identify(name)`, `code`, desc))
529 def checkforenum(self, enum):
530 """This enum code is used by an event. Make sure it's available"""
531 name, fullname, module = self.findcodename('enum', enum)
532 if not name:
533 self.fp.write("# XXXX enum %s not found!!\n"%(enum))
534 return
535 if module:
536 self.fp.write("from %s import %s\n"%(module, name))
538 def dumpindex(self):
539 self.fp.write("\n#\n# Indices of types declared in this module\n#\n")
540 self.fp.write("_classdeclarations = {\n")
541 for k in self.classcodes.keys():
542 self.fp.write("\t%s : %s,\n" % (`k`, self.classcodes[k]))
543 self.fp.write("}\n")
544 self.fp.write("\n_propdeclarations = {\n")
545 for k in self.propcodes.keys():
546 self.fp.write("\t%s : %s,\n" % (`k`, self.propcodes[k]))
547 self.fp.write("}\n")
548 self.fp.write("\n_compdeclarations = {\n")
549 for k in self.compcodes.keys():
550 self.fp.write("\t%s : %s,\n" % (`k`, self.compcodes[k]))
551 self.fp.write("}\n")
552 self.fp.write("\n_enumdeclarations = {\n")
553 for k in self.enumcodes.keys():
554 self.fp.write("\t%s : %s,\n" % (`k`, self.enumcodes[k]))
555 self.fp.write("}\n")
557 def compiledata(data):
558 [type, description, flags] = data
559 return "%s -- %s %s" % (`type`, `description`, compiledataflags(flags))
561 def is_null(data):
562 return data[0] == 'null'
564 def is_optional(data):
565 return (data[2] & 0x8000)
567 def is_enum(data):
568 return (data[2] & 0x2000)
570 def getdatadoc(data):
571 [type, descr, flags] = data
572 if descr:
573 return descr
574 if type == '****':
575 return 'anything'
576 if type == 'obj ':
577 return 'an AE object reference'
578 return "undocumented, typecode %s"%`type`
580 dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
581 def compiledataflags(flags):
582 bits = []
583 for i in range(16):
584 if flags & (1<<i):
585 if i in dataflagdict.keys():
586 bits.append(dataflagdict[i])
587 else:
588 bits.append(`i`)
589 return '[%s]' % string.join(bits)
591 # XXXX Do we have a set of python keywords somewhere?
592 illegal_ids = [ "for", "in", "from", "and", "or", "not", "print", "class", "return",
593 "def" ]
595 def identify(str):
596 """Turn any string into an identifier:
597 - replace space by _
598 - replace other illegal chars by _xx_ (hex code)
599 - prepend _ if the result is a python keyword
601 if not str:
602 return "_empty_ae_name"
603 rv = ''
604 ok = string.letters + '_'
605 ok2 = ok + string.digits
606 for c in str:
607 if c in ok:
608 rv = rv + c
609 elif c == ' ':
610 rv = rv + '_'
611 else:
612 rv = rv + '_%02.2x_'%ord(c)
613 ok = ok2
614 if rv in illegal_ids:
615 rv = '_' + rv
616 return rv
618 # Call the main program
620 if __name__ == '__main__':
621 main()
622 sys.exit(1)