Add better error reporting for MemoryErrors caused by str->float conversions.
[python.git] / Lib / plat-mac / gensuitemodule.py
blob28ebea6a4aa32163f328e53777e74aeb5f8824ae
1 """
2 gensuitemodule - Generate an AE suite module from an aete/aeut resource
4 Based on aete.py.
6 Reading and understanding this code is left as an exercise to the reader.
7 """
9 from warnings import warnpy3k
10 warnpy3k("In 3.x, the gensuitemodule module is removed.", stacklevel=2)
12 import MacOS
13 import EasyDialogs
14 import os
15 import string
16 import sys
17 import types
18 import StringIO
19 import keyword
20 import macresource
21 import aetools
22 import distutils.sysconfig
23 import OSATerminology
24 from Carbon.Res import *
25 import Carbon.Folder
26 import MacOS
27 import getopt
28 import plistlib
30 _MAC_LIB_FOLDER=os.path.dirname(aetools.__file__)
31 DEFAULT_STANDARD_PACKAGEFOLDER=os.path.join(_MAC_LIB_FOLDER, 'lib-scriptpackages')
32 DEFAULT_USER_PACKAGEFOLDER=distutils.sysconfig.get_python_lib()
34 def usage():
35 sys.stderr.write("Usage: %s [opts] application-or-resource-file\n" % sys.argv[0])
36 sys.stderr.write("""Options:
37 --output pkgdir Pathname of the output package (short: -o)
38 --resource Parse resource file in stead of launching application (-r)
39 --base package Use another base package in stead of default StdSuites (-b)
40 --edit old=new Edit suite names, use empty new to skip a suite (-e)
41 --creator code Set creator code for package (-c)
42 --dump Dump aete resource to stdout in stead of creating module (-d)
43 --verbose Tell us what happens (-v)
44 """)
45 sys.exit(1)
47 def main():
48 if len(sys.argv) > 1:
49 SHORTOPTS = "rb:o:e:c:dv"
50 LONGOPTS = ("resource", "base=", "output=", "edit=", "creator=", "dump", "verbose")
51 try:
52 opts, args = getopt.getopt(sys.argv[1:], SHORTOPTS, LONGOPTS)
53 except getopt.GetoptError:
54 usage()
56 process_func = processfile
57 basepkgname = 'StdSuites'
58 output = None
59 edit_modnames = []
60 creatorsignature = None
61 dump = None
62 verbose = None
64 for o, a in opts:
65 if o in ('-r', '--resource'):
66 process_func = processfile_fromresource
67 if o in ('-b', '--base'):
68 basepkgname = a
69 if o in ('-o', '--output'):
70 output = a
71 if o in ('-e', '--edit'):
72 split = a.split('=')
73 if len(split) != 2:
74 usage()
75 edit_modnames.append(split)
76 if o in ('-c', '--creator'):
77 if len(a) != 4:
78 sys.stderr.write("creator must be 4-char string\n")
79 sys.exit(1)
80 creatorsignature = a
81 if o in ('-d', '--dump'):
82 dump = sys.stdout
83 if o in ('-v', '--verbose'):
84 verbose = sys.stderr
87 if output and len(args) > 1:
88 sys.stderr.write("%s: cannot specify --output with multiple inputs\n" % sys.argv[0])
89 sys.exit(1)
91 for filename in args:
92 process_func(filename, output=output, basepkgname=basepkgname,
93 edit_modnames=edit_modnames, creatorsignature=creatorsignature,
94 dump=dump, verbose=verbose)
95 else:
96 main_interactive()
98 def main_interactive(interact=0, basepkgname='StdSuites'):
99 if interact:
100 # Ask for save-filename for each module
101 edit_modnames = None
102 else:
103 # Use default filenames for each module
104 edit_modnames = []
105 appsfolder = Carbon.Folder.FSFindFolder(-32765, 'apps', 0)
106 filename = EasyDialogs.AskFileForOpen(
107 message='Select scriptable application',
108 dialogOptionFlags=0x1056, # allow selection of .app bundles
109 defaultLocation=appsfolder)
110 if not filename:
111 return
112 if not is_scriptable(filename):
113 if EasyDialogs.AskYesNoCancel(
114 "Warning: application does not seem scriptable",
115 yes="Continue", default=2, no="") <= 0:
116 return
117 try:
118 processfile(filename, edit_modnames=edit_modnames, basepkgname=basepkgname,
119 verbose=sys.stderr)
120 except MacOS.Error, arg:
121 print "Error getting terminology:", arg
122 print "Retry, manually parsing resources"
123 processfile_fromresource(filename, edit_modnames=edit_modnames,
124 basepkgname=basepkgname, verbose=sys.stderr)
126 def is_scriptable(application):
127 """Return true if the application is scriptable"""
128 if os.path.isdir(application):
129 plistfile = os.path.join(application, 'Contents', 'Info.plist')
130 if not os.path.exists(plistfile):
131 return False
132 plist = plistlib.Plist.fromFile(plistfile)
133 return plist.get('NSAppleScriptEnabled', False)
134 # If it is a file test for an aete/aeut resource.
135 currf = CurResFile()
136 try:
137 refno = macresource.open_pathname(application)
138 except MacOS.Error:
139 return False
140 UseResFile(refno)
141 n_terminology = Count1Resources('aete') + Count1Resources('aeut') + \
142 Count1Resources('scsz') + Count1Resources('osiz')
143 CloseResFile(refno)
144 UseResFile(currf)
145 return n_terminology > 0
147 def processfile_fromresource(fullname, output=None, basepkgname=None,
148 edit_modnames=None, creatorsignature=None, dump=None, verbose=None):
149 """Process all resources in a single file"""
150 if not is_scriptable(fullname) and verbose:
151 print >>verbose, "Warning: app does not seem scriptable: %s" % fullname
152 cur = CurResFile()
153 if verbose:
154 print >>verbose, "Processing", fullname
155 rf = macresource.open_pathname(fullname)
156 try:
157 UseResFile(rf)
158 resources = []
159 for i in range(Count1Resources('aete')):
160 res = Get1IndResource('aete', 1+i)
161 resources.append(res)
162 for i in range(Count1Resources('aeut')):
163 res = Get1IndResource('aeut', 1+i)
164 resources.append(res)
165 if verbose:
166 print >>verbose, "\nLISTING aete+aeut RESOURCES IN", repr(fullname)
167 aetelist = []
168 for res in resources:
169 if verbose:
170 print >>verbose, "decoding", res.GetResInfo(), "..."
171 data = res.data
172 aete = decode(data, verbose)
173 aetelist.append((aete, res.GetResInfo()))
174 finally:
175 if rf <> cur:
176 CloseResFile(rf)
177 UseResFile(cur)
178 # switch back (needed for dialogs in Python)
179 UseResFile(cur)
180 if dump:
181 dumpaetelist(aetelist, dump)
182 compileaetelist(aetelist, fullname, output=output,
183 basepkgname=basepkgname, edit_modnames=edit_modnames,
184 creatorsignature=creatorsignature, verbose=verbose)
186 def processfile(fullname, output=None, basepkgname=None,
187 edit_modnames=None, creatorsignature=None, dump=None,
188 verbose=None):
189 """Ask an application for its terminology and process that"""
190 if not is_scriptable(fullname) and verbose:
191 print >>verbose, "Warning: app does not seem scriptable: %s" % fullname
192 if verbose:
193 print >>verbose, "\nASKING FOR aete DICTIONARY IN", repr(fullname)
194 try:
195 aedescobj, launched = OSATerminology.GetAppTerminology(fullname)
196 except MacOS.Error, arg:
197 if arg[0] in (-1701, -192): # errAEDescNotFound, resNotFound
198 if verbose:
199 print >>verbose, "GetAppTerminology failed with errAEDescNotFound/resNotFound, trying manually"
200 aedata, sig = getappterminology(fullname, verbose=verbose)
201 if not creatorsignature:
202 creatorsignature = sig
203 else:
204 raise
205 else:
206 if launched:
207 if verbose:
208 print >>verbose, "Launched", fullname
209 raw = aetools.unpack(aedescobj)
210 if not raw:
211 if verbose:
212 print >>verbose, 'Unpack returned empty value:', raw
213 return
214 if not raw[0].data:
215 if verbose:
216 print >>verbose, 'Unpack returned value without data:', raw
217 return
218 aedata = raw[0]
219 aete = decode(aedata.data, verbose)
220 if dump:
221 dumpaetelist([aete], dump)
222 return
223 compileaete(aete, None, fullname, output=output, basepkgname=basepkgname,
224 creatorsignature=creatorsignature, edit_modnames=edit_modnames,
225 verbose=verbose)
227 def getappterminology(fullname, verbose=None):
228 """Get application terminology by sending an AppleEvent"""
229 # First check that we actually can send AppleEvents
230 if not MacOS.WMAvailable():
231 raise RuntimeError, "Cannot send AppleEvents, no access to window manager"
232 # Next, a workaround for a bug in MacOS 10.2: sending events will hang unless
233 # you have created an event loop first.
234 import Carbon.Evt
235 Carbon.Evt.WaitNextEvent(0,0)
236 if os.path.isdir(fullname):
237 # Now get the signature of the application, hoping it is a bundle
238 pkginfo = os.path.join(fullname, 'Contents', 'PkgInfo')
239 if not os.path.exists(pkginfo):
240 raise RuntimeError, "No PkgInfo file found"
241 tp_cr = open(pkginfo, 'rb').read()
242 cr = tp_cr[4:8]
243 else:
244 # Assume it is a file
245 cr, tp = MacOS.GetCreatorAndType(fullname)
246 # Let's talk to it and ask for its AETE
247 talker = aetools.TalkTo(cr)
248 try:
249 talker._start()
250 except (MacOS.Error, aetools.Error), arg:
251 if verbose:
252 print >>verbose, 'Warning: start() failed, continuing anyway:', arg
253 reply = talker.send("ascr", "gdte")
254 #reply2 = talker.send("ascr", "gdut")
255 # Now pick the bits out of the return that we need.
256 return reply[1]['----'], cr
259 def compileaetelist(aetelist, fullname, output=None, basepkgname=None,
260 edit_modnames=None, creatorsignature=None, verbose=None):
261 for aete, resinfo in aetelist:
262 compileaete(aete, resinfo, fullname, output=output,
263 basepkgname=basepkgname, edit_modnames=edit_modnames,
264 creatorsignature=creatorsignature, verbose=verbose)
266 def dumpaetelist(aetelist, output):
267 import pprint
268 pprint.pprint(aetelist, output)
270 def decode(data, verbose=None):
271 """Decode a resource into a python data structure"""
272 f = StringIO.StringIO(data)
273 aete = generic(getaete, f)
274 aete = simplify(aete)
275 processed = f.tell()
276 unprocessed = len(f.read())
277 total = f.tell()
278 if unprocessed and verbose:
279 verbose.write("%d processed + %d unprocessed = %d total\n" %
280 (processed, unprocessed, total))
281 return aete
283 def simplify(item):
284 """Recursively replace singleton tuples by their constituent item"""
285 if type(item) is types.ListType:
286 return map(simplify, item)
287 elif type(item) == types.TupleType and len(item) == 2:
288 return simplify(item[1])
289 else:
290 return item
293 # Here follows the aete resource decoder.
294 # It is presented bottom-up instead of top-down because there are direct
295 # references to the lower-level part-decoders from the high-level part-decoders.
297 def getbyte(f, *args):
298 c = f.read(1)
299 if not c:
300 raise EOFError, 'in getbyte' + str(args)
301 return ord(c)
303 def getword(f, *args):
304 getalign(f)
305 s = f.read(2)
306 if len(s) < 2:
307 raise EOFError, 'in getword' + str(args)
308 return (ord(s[0])<<8) | ord(s[1])
310 def getlong(f, *args):
311 getalign(f)
312 s = f.read(4)
313 if len(s) < 4:
314 raise EOFError, 'in getlong' + str(args)
315 return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3])
317 def getostype(f, *args):
318 getalign(f)
319 s = f.read(4)
320 if len(s) < 4:
321 raise EOFError, 'in getostype' + str(args)
322 return s
324 def getpstr(f, *args):
325 c = f.read(1)
326 if len(c) < 1:
327 raise EOFError, 'in getpstr[1]' + str(args)
328 nbytes = ord(c)
329 if nbytes == 0: return ''
330 s = f.read(nbytes)
331 if len(s) < nbytes:
332 raise EOFError, 'in getpstr[2]' + str(args)
333 return s
335 def getalign(f):
336 if f.tell() & 1:
337 c = f.read(1)
338 ##if c <> '\0':
339 ## print align:', repr(c)
341 def getlist(f, description, getitem):
342 count = getword(f)
343 list = []
344 for i in range(count):
345 list.append(generic(getitem, f))
346 getalign(f)
347 return list
349 def alt_generic(what, f, *args):
350 print "generic", repr(what), args
351 res = vageneric(what, f, args)
352 print '->', repr(res)
353 return res
355 def generic(what, f, *args):
356 if type(what) == types.FunctionType:
357 return apply(what, (f,) + args)
358 if type(what) == types.ListType:
359 record = []
360 for thing in what:
361 item = apply(generic, thing[:1] + (f,) + thing[1:])
362 record.append((thing[1], item))
363 return record
364 return "BAD GENERIC ARGS: %r" % (what,)
366 getdata = [
367 (getostype, "type"),
368 (getpstr, "description"),
369 (getword, "flags")
371 getargument = [
372 (getpstr, "name"),
373 (getostype, "keyword"),
374 (getdata, "what")
376 getevent = [
377 (getpstr, "name"),
378 (getpstr, "description"),
379 (getostype, "suite code"),
380 (getostype, "event code"),
381 (getdata, "returns"),
382 (getdata, "accepts"),
383 (getlist, "optional arguments", getargument)
385 getproperty = [
386 (getpstr, "name"),
387 (getostype, "code"),
388 (getdata, "what")
390 getelement = [
391 (getostype, "type"),
392 (getlist, "keyform", getostype)
394 getclass = [
395 (getpstr, "name"),
396 (getostype, "class code"),
397 (getpstr, "description"),
398 (getlist, "properties", getproperty),
399 (getlist, "elements", getelement)
401 getcomparison = [
402 (getpstr, "operator name"),
403 (getostype, "operator ID"),
404 (getpstr, "operator comment"),
406 getenumerator = [
407 (getpstr, "enumerator name"),
408 (getostype, "enumerator ID"),
409 (getpstr, "enumerator comment")
411 getenumeration = [
412 (getostype, "enumeration ID"),
413 (getlist, "enumerator", getenumerator)
415 getsuite = [
416 (getpstr, "suite name"),
417 (getpstr, "suite description"),
418 (getostype, "suite ID"),
419 (getword, "suite level"),
420 (getword, "suite version"),
421 (getlist, "events", getevent),
422 (getlist, "classes", getclass),
423 (getlist, "comparisons", getcomparison),
424 (getlist, "enumerations", getenumeration)
426 getaete = [
427 (getword, "major/minor version in BCD"),
428 (getword, "language code"),
429 (getword, "script code"),
430 (getlist, "suites", getsuite)
433 def compileaete(aete, resinfo, fname, output=None, basepkgname=None,
434 edit_modnames=None, creatorsignature=None, verbose=None):
435 """Generate code for a full aete resource. fname passed for doc purposes"""
436 [version, language, script, suites] = aete
437 major, minor = divmod(version, 256)
438 if not creatorsignature:
439 creatorsignature, dummy = MacOS.GetCreatorAndType(fname)
440 packagename = identify(os.path.splitext(os.path.basename(fname))[0])
441 if language:
442 packagename = packagename+'_lang%d'%language
443 if script:
444 packagename = packagename+'_script%d'%script
445 if len(packagename) > 27:
446 packagename = packagename[:27]
447 if output:
448 # XXXX Put this in site-packages if it isn't a full pathname?
449 if not os.path.exists(output):
450 os.mkdir(output)
451 pathname = output
452 else:
453 pathname = EasyDialogs.AskFolder(message='Create and select package folder for %s'%packagename,
454 defaultLocation=DEFAULT_USER_PACKAGEFOLDER)
455 output = pathname
456 if not pathname:
457 return
458 packagename = os.path.split(os.path.normpath(pathname))[1]
459 if not basepkgname:
460 basepkgname = EasyDialogs.AskFolder(message='Package folder for base suite (usually StdSuites)',
461 defaultLocation=DEFAULT_STANDARD_PACKAGEFOLDER)
462 if basepkgname:
463 dirname, basepkgname = os.path.split(os.path.normpath(basepkgname))
464 if dirname and not dirname in sys.path:
465 sys.path.insert(0, dirname)
466 basepackage = __import__(basepkgname)
467 else:
468 basepackage = None
469 suitelist = []
470 allprecompinfo = []
471 allsuites = []
472 for suite in suites:
473 compiler = SuiteCompiler(suite, basepackage, output, edit_modnames, verbose)
474 code, modname, precompinfo = compiler.precompilesuite()
475 if not code:
476 continue
477 allprecompinfo = allprecompinfo + precompinfo
478 suiteinfo = suite, pathname, modname
479 suitelist.append((code, modname))
480 allsuites.append(compiler)
481 for compiler in allsuites:
482 compiler.compilesuite(major, minor, language, script, fname, allprecompinfo)
483 initfilename = os.path.join(output, '__init__.py')
484 fp = open(initfilename, 'w')
485 MacOS.SetCreatorAndType(initfilename, 'Pyth', 'TEXT')
486 fp.write('"""\n')
487 fp.write("Package generated from %s\n"%ascii(fname))
488 if resinfo:
489 fp.write("Resource %s resid %d %s\n"%(ascii(resinfo[1]), resinfo[0], ascii(resinfo[2])))
490 fp.write('"""\n')
491 fp.write('import aetools\n')
492 fp.write('Error = aetools.Error\n')
493 suitelist.sort()
494 for code, modname in suitelist:
495 fp.write("import %s\n" % modname)
496 fp.write("\n\n_code_to_module = {\n")
497 for code, modname in suitelist:
498 fp.write(" '%s' : %s,\n"%(ascii(code), modname))
499 fp.write("}\n\n")
500 fp.write("\n\n_code_to_fullname = {\n")
501 for code, modname in suitelist:
502 fp.write(" '%s' : ('%s.%s', '%s'),\n"%(ascii(code), packagename, modname, modname))
503 fp.write("}\n\n")
504 for code, modname in suitelist:
505 fp.write("from %s import *\n"%modname)
507 # Generate property dicts and element dicts for all types declared in this module
508 fp.write("\ndef getbaseclasses(v):\n")
509 fp.write(" if not getattr(v, '_propdict', None):\n")
510 fp.write(" v._propdict = {}\n")
511 fp.write(" v._elemdict = {}\n")
512 fp.write(" for superclassname in getattr(v, '_superclassnames', []):\n")
513 fp.write(" superclass = eval(superclassname)\n")
514 fp.write(" getbaseclasses(superclass)\n")
515 fp.write(" v._propdict.update(getattr(superclass, '_propdict', {}))\n")
516 fp.write(" v._elemdict.update(getattr(superclass, '_elemdict', {}))\n")
517 fp.write(" v._propdict.update(getattr(v, '_privpropdict', {}))\n")
518 fp.write(" v._elemdict.update(getattr(v, '_privelemdict', {}))\n")
519 fp.write("\n")
520 fp.write("import StdSuites\n")
521 allprecompinfo.sort()
522 if allprecompinfo:
523 fp.write("\n#\n# Set property and element dictionaries now that all classes have been defined\n#\n")
524 for codenamemapper in allprecompinfo:
525 for k, v in codenamemapper.getall('class'):
526 fp.write("getbaseclasses(%s)\n" % v)
528 # Generate a code-to-name mapper for all of the types (classes) declared in this module
529 application_class = None
530 if allprecompinfo:
531 fp.write("\n#\n# Indices of types declared in this module\n#\n")
532 fp.write("_classdeclarations = {\n")
533 for codenamemapper in allprecompinfo:
534 for k, v in codenamemapper.getall('class'):
535 fp.write(" %r : %s,\n" % (k, v))
536 if k == 'capp':
537 application_class = v
538 fp.write("}\n")
541 if suitelist:
542 fp.write("\n\nclass %s(%s_Events"%(packagename, suitelist[0][1]))
543 for code, modname in suitelist[1:]:
544 fp.write(",\n %s_Events"%modname)
545 fp.write(",\n aetools.TalkTo):\n")
546 fp.write(" _signature = %r\n\n"%(creatorsignature,))
547 fp.write(" _moduleName = '%s'\n\n"%packagename)
548 if application_class:
549 fp.write(" _elemdict = %s._elemdict\n" % application_class)
550 fp.write(" _propdict = %s._propdict\n" % application_class)
551 fp.close()
553 class SuiteCompiler:
554 def __init__(self, suite, basepackage, output, edit_modnames, verbose):
555 self.suite = suite
556 self.basepackage = basepackage
557 self.edit_modnames = edit_modnames
558 self.output = output
559 self.verbose = verbose
561 # Set by precompilesuite
562 self.pathname = None
563 self.modname = None
565 # Set by compilesuite
566 self.fp = None
567 self.basemodule = None
568 self.enumsneeded = {}
570 def precompilesuite(self):
571 """Parse a single suite without generating the output. This step is needed
572 so we can resolve recursive references by suites to enums/comps/etc declared
573 in other suites"""
574 [name, desc, code, level, version, events, classes, comps, enums] = self.suite
576 modname = identify(name)
577 if len(modname) > 28:
578 modname = modname[:27]
579 if self.edit_modnames is None:
580 self.pathname = EasyDialogs.AskFileForSave(message='Python output file',
581 savedFileName=modname+'.py')
582 else:
583 for old, new in self.edit_modnames:
584 if old == modname:
585 modname = new
586 if modname:
587 self.pathname = os.path.join(self.output, modname + '.py')
588 else:
589 self.pathname = None
590 if not self.pathname:
591 return None, None, None
593 self.modname = os.path.splitext(os.path.split(self.pathname)[1])[0]
595 if self.basepackage and self.basepackage._code_to_module.has_key(code):
596 # We are an extension of a baseclass (usually an application extending
597 # Standard_Suite or so). Import everything from our base module
598 basemodule = self.basepackage._code_to_module[code]
599 else:
600 # We are not an extension.
601 basemodule = None
603 self.enumsneeded = {}
604 for event in events:
605 self.findenumsinevent(event)
607 objc = ObjectCompiler(None, self.modname, basemodule, interact=(self.edit_modnames is None),
608 verbose=self.verbose)
609 for cls in classes:
610 objc.compileclass(cls)
611 for cls in classes:
612 objc.fillclasspropsandelems(cls)
613 for comp in comps:
614 objc.compilecomparison(comp)
615 for enum in enums:
616 objc.compileenumeration(enum)
618 for enum in self.enumsneeded.keys():
619 objc.checkforenum(enum)
621 objc.dumpindex()
623 precompinfo = objc.getprecompinfo(self.modname)
625 return code, self.modname, precompinfo
627 def compilesuite(self, major, minor, language, script, fname, precompinfo):
628 """Generate code for a single suite"""
629 [name, desc, code, level, version, events, classes, comps, enums] = self.suite
630 # Sort various lists, so re-generated source is easier compared
631 def class_sorter(k1, k2):
632 """Sort classes by code, and make sure main class sorts before synonyms"""
633 # [name, code, desc, properties, elements] = cls
634 if k1[1] < k2[1]: return -1
635 if k1[1] > k2[1]: return 1
636 if not k2[3] or k2[3][0][1] == 'c@#!':
637 # This is a synonym, the other one is better
638 return -1
639 if not k1[3] or k1[3][0][1] == 'c@#!':
640 # This is a synonym, the other one is better
641 return 1
642 return 0
644 events.sort()
645 classes.sort(class_sorter)
646 comps.sort()
647 enums.sort()
649 self.fp = fp = open(self.pathname, 'w')
650 MacOS.SetCreatorAndType(self.pathname, 'Pyth', 'TEXT')
652 fp.write('"""Suite %s: %s\n' % (ascii(name), ascii(desc)))
653 fp.write("Level %d, version %d\n\n" % (level, version))
654 fp.write("Generated from %s\n"%ascii(fname))
655 fp.write("AETE/AEUT resource version %d/%d, language %d, script %d\n" % \
656 (major, minor, language, script))
657 fp.write('"""\n\n')
659 fp.write('import aetools\n')
660 fp.write('import MacOS\n\n')
661 fp.write("_code = %r\n\n"% (code,))
662 if self.basepackage and self.basepackage._code_to_module.has_key(code):
663 # We are an extension of a baseclass (usually an application extending
664 # Standard_Suite or so). Import everything from our base module
665 fp.write('from %s import *\n'%self.basepackage._code_to_fullname[code][0])
666 basemodule = self.basepackage._code_to_module[code]
667 elif self.basepackage and self.basepackage._code_to_module.has_key(code.lower()):
668 # This is needed by CodeWarrior and some others.
669 fp.write('from %s import *\n'%self.basepackage._code_to_fullname[code.lower()][0])
670 basemodule = self.basepackage._code_to_module[code.lower()]
671 else:
672 # We are not an extension.
673 basemodule = None
674 self.basemodule = basemodule
675 self.compileclassheader()
677 self.enumsneeded = {}
678 if events:
679 for event in events:
680 self.compileevent(event)
681 else:
682 fp.write(" pass\n\n")
684 objc = ObjectCompiler(fp, self.modname, basemodule, precompinfo, interact=(self.edit_modnames is None),
685 verbose=self.verbose)
686 for cls in classes:
687 objc.compileclass(cls)
688 for cls in classes:
689 objc.fillclasspropsandelems(cls)
690 for comp in comps:
691 objc.compilecomparison(comp)
692 for enum in enums:
693 objc.compileenumeration(enum)
695 for enum in self.enumsneeded.keys():
696 objc.checkforenum(enum)
698 objc.dumpindex()
700 def compileclassheader(self):
701 """Generate class boilerplate"""
702 classname = '%s_Events'%self.modname
703 if self.basemodule:
704 modshortname = string.split(self.basemodule.__name__, '.')[-1]
705 baseclassname = '%s_Events'%modshortname
706 self.fp.write("class %s(%s):\n\n"%(classname, baseclassname))
707 else:
708 self.fp.write("class %s:\n\n"%classname)
710 def compileevent(self, event):
711 """Generate code for a single event"""
712 [name, desc, code, subcode, returns, accepts, arguments] = event
713 fp = self.fp
714 funcname = identify(name)
716 # generate name->keyword map
718 if arguments:
719 fp.write(" _argmap_%s = {\n"%funcname)
720 for a in arguments:
721 fp.write(" %r : %r,\n"%(identify(a[0]), a[1]))
722 fp.write(" }\n\n")
725 # Generate function header
727 has_arg = (not is_null(accepts))
728 opt_arg = (has_arg and is_optional(accepts))
730 fp.write(" def %s(self, "%funcname)
731 if has_arg:
732 if not opt_arg:
733 fp.write("_object, ") # Include direct object, if it has one
734 else:
735 fp.write("_object=None, ") # Also include if it is optional
736 else:
737 fp.write("_no_object=None, ") # For argument checking
738 fp.write("_attributes={}, **_arguments):\n") # include attribute dict and args
740 # Generate doc string (important, since it may be the only
741 # available documentation, due to our name-remaping)
743 fp.write(' """%s: %s\n'%(ascii(name), ascii(desc)))
744 if has_arg:
745 fp.write(" Required argument: %s\n"%getdatadoc(accepts))
746 elif opt_arg:
747 fp.write(" Optional argument: %s\n"%getdatadoc(accepts))
748 for arg in arguments:
749 fp.write(" Keyword argument %s: %s\n"%(identify(arg[0]),
750 getdatadoc(arg[2])))
751 fp.write(" Keyword argument _attributes: AppleEvent attribute dictionary\n")
752 if not is_null(returns):
753 fp.write(" Returns: %s\n"%getdatadoc(returns))
754 fp.write(' """\n')
756 # Fiddle the args so everything ends up in 'arguments' dictionary
758 fp.write(" _code = %r\n"% (code,))
759 fp.write(" _subcode = %r\n\n"% (subcode,))
761 # Do keyword name substitution
763 if arguments:
764 fp.write(" aetools.keysubst(_arguments, self._argmap_%s)\n"%funcname)
765 else:
766 fp.write(" if _arguments: raise TypeError, 'No optional args expected'\n")
768 # Stuff required arg (if there is one) into arguments
770 if has_arg:
771 fp.write(" _arguments['----'] = _object\n")
772 elif opt_arg:
773 fp.write(" if _object:\n")
774 fp.write(" _arguments['----'] = _object\n")
775 else:
776 fp.write(" if _no_object is not None: raise TypeError, 'No direct arg expected'\n")
777 fp.write("\n")
779 # Do enum-name substitution
781 for a in arguments:
782 if is_enum(a[2]):
783 kname = a[1]
784 ename = a[2][0]
785 if ename <> '****':
786 fp.write(" aetools.enumsubst(_arguments, %r, _Enum_%s)\n" %
787 (kname, identify(ename)))
788 self.enumsneeded[ename] = 1
789 fp.write("\n")
791 # Do the transaction
793 fp.write(" _reply, _arguments, _attributes = self.send(_code, _subcode,\n")
794 fp.write(" _arguments, _attributes)\n")
796 # Error handling
798 fp.write(" if _arguments.get('errn', 0):\n")
799 fp.write(" raise aetools.Error, aetools.decodeerror(_arguments)\n")
800 fp.write(" # XXXX Optionally decode result\n")
802 # Decode result
804 fp.write(" if _arguments.has_key('----'):\n")
805 if is_enum(returns):
806 fp.write(" # XXXX Should do enum remapping here...\n")
807 fp.write(" return _arguments['----']\n")
808 fp.write("\n")
810 def findenumsinevent(self, event):
811 """Find all enums for a single event"""
812 [name, desc, code, subcode, returns, accepts, arguments] = event
813 for a in arguments:
814 if is_enum(a[2]):
815 ename = a[2][0]
816 if ename <> '****':
817 self.enumsneeded[ename] = 1
820 # This class stores the code<->name translations for a single module. It is used
821 # to keep the information while we're compiling the module, but we also keep these objects
822 # around so if one suite refers to, say, an enum in another suite we know where to
823 # find it. Finally, if we really can't find a code, the user can add modules by
824 # hand.
826 class CodeNameMapper:
828 def __init__(self, interact=1, verbose=None):
829 self.code2name = {
830 "property" : {},
831 "class" : {},
832 "enum" : {},
833 "comparison" : {},
835 self.name2code = {
836 "property" : {},
837 "class" : {},
838 "enum" : {},
839 "comparison" : {},
841 self.modulename = None
842 self.star_imported = 0
843 self.can_interact = interact
844 self.verbose = verbose
846 def addnamecode(self, type, name, code):
847 self.name2code[type][name] = code
848 if not self.code2name[type].has_key(code):
849 self.code2name[type][code] = name
851 def hasname(self, name):
852 for dict in self.name2code.values():
853 if dict.has_key(name):
854 return True
855 return False
857 def hascode(self, type, code):
858 return self.code2name[type].has_key(code)
860 def findcodename(self, type, code):
861 if not self.hascode(type, code):
862 return None, None, None
863 name = self.code2name[type][code]
864 if self.modulename and not self.star_imported:
865 qualname = '%s.%s'%(self.modulename, name)
866 else:
867 qualname = name
868 return name, qualname, self.modulename
870 def getall(self, type):
871 return self.code2name[type].items()
873 def addmodule(self, module, name, star_imported):
874 self.modulename = name
875 self.star_imported = star_imported
876 for code, name in module._propdeclarations.items():
877 self.addnamecode('property', name, code)
878 for code, name in module._classdeclarations.items():
879 self.addnamecode('class', name, code)
880 for code in module._enumdeclarations.keys():
881 self.addnamecode('enum', '_Enum_'+identify(code), code)
882 for code, name in module._compdeclarations.items():
883 self.addnamecode('comparison', name, code)
885 def prepareforexport(self, name=None):
886 if not self.modulename:
887 self.modulename = name
888 return self
890 class ObjectCompiler:
891 def __init__(self, fp, modname, basesuite, othernamemappers=None, interact=1,
892 verbose=None):
893 self.fp = fp
894 self.verbose = verbose
895 self.basesuite = basesuite
896 self.can_interact = interact
897 self.modulename = modname
898 self.namemappers = [CodeNameMapper(self.can_interact, self.verbose)]
899 if othernamemappers:
900 self.othernamemappers = othernamemappers[:]
901 else:
902 self.othernamemappers = []
903 if basesuite:
904 basemapper = CodeNameMapper(self.can_interact, self.verbose)
905 basemapper.addmodule(basesuite, '', 1)
906 self.namemappers.append(basemapper)
908 def getprecompinfo(self, modname):
909 list = []
910 for mapper in self.namemappers:
911 emapper = mapper.prepareforexport(modname)
912 if emapper:
913 list.append(emapper)
914 return list
916 def findcodename(self, type, code):
917 while 1:
918 # First try: check whether we already know about this code.
919 for mapper in self.namemappers:
920 if mapper.hascode(type, code):
921 return mapper.findcodename(type, code)
922 # Second try: maybe one of the other modules knows about it.
923 for mapper in self.othernamemappers:
924 if mapper.hascode(type, code):
925 self.othernamemappers.remove(mapper)
926 self.namemappers.append(mapper)
927 if self.fp:
928 self.fp.write("import %s\n"%mapper.modulename)
929 break
930 else:
931 # If all this has failed we ask the user for a guess on where it could
932 # be and retry.
933 if self.fp:
934 m = self.askdefinitionmodule(type, code)
935 else:
936 m = None
937 if not m: return None, None, None
938 mapper = CodeNameMapper(self.can_interact, self.verbose)
939 mapper.addmodule(m, m.__name__, 0)
940 self.namemappers.append(mapper)
942 def hasname(self, name):
943 for mapper in self.othernamemappers:
944 if mapper.hasname(name) and mapper.modulename != self.modulename:
945 if self.verbose:
946 print >>self.verbose, "Duplicate Python identifier:", name, self.modulename, mapper.modulename
947 return True
948 return False
950 def askdefinitionmodule(self, type, code):
951 if not self.can_interact:
952 if self.verbose:
953 print >>self.verbose, "** No definition for %s '%s' found" % (type, code)
954 return None
955 path = EasyDialogs.AskFileForSave(message='Where is %s %s declared?'%(type, code))
956 if not path: return
957 path, file = os.path.split(path)
958 modname = os.path.splitext(file)[0]
959 if not path in sys.path:
960 sys.path.insert(0, path)
961 m = __import__(modname)
962 self.fp.write("import %s\n"%modname)
963 return m
965 def compileclass(self, cls):
966 [name, code, desc, properties, elements] = cls
967 pname = identify(name)
968 if self.namemappers[0].hascode('class', code):
969 # plural forms and such
970 othername, dummy, dummy = self.namemappers[0].findcodename('class', code)
971 if self.fp:
972 self.fp.write("\n%s = %s\n"%(pname, othername))
973 else:
974 if self.fp:
975 self.fp.write('\nclass %s(aetools.ComponentItem):\n' % pname)
976 self.fp.write(' """%s - %s """\n' % (ascii(name), ascii(desc)))
977 self.fp.write(' want = %r\n' % (code,))
978 self.namemappers[0].addnamecode('class', pname, code)
979 is_application_class = (code == 'capp')
980 properties.sort()
981 for prop in properties:
982 self.compileproperty(prop, is_application_class)
983 elements.sort()
984 for elem in elements:
985 self.compileelement(elem)
987 def compileproperty(self, prop, is_application_class=False):
988 [name, code, what] = prop
989 if code == 'c@#!':
990 # Something silly with plurals. Skip it.
991 return
992 pname = identify(name)
993 if self.namemappers[0].hascode('property', code):
994 # plural forms and such
995 othername, dummy, dummy = self.namemappers[0].findcodename('property', code)
996 if pname == othername:
997 return
998 if self.fp:
999 self.fp.write("\n_Prop_%s = _Prop_%s\n"%(pname, othername))
1000 else:
1001 if self.fp:
1002 self.fp.write("class _Prop_%s(aetools.NProperty):\n" % pname)
1003 self.fp.write(' """%s - %s """\n' % (ascii(name), ascii(what[1])))
1004 self.fp.write(" which = %r\n" % (code,))
1005 self.fp.write(" want = %r\n" % (what[0],))
1006 self.namemappers[0].addnamecode('property', pname, code)
1007 if is_application_class and self.fp:
1008 self.fp.write("%s = _Prop_%s()\n" % (pname, pname))
1010 def compileelement(self, elem):
1011 [code, keyform] = elem
1012 if self.fp:
1013 self.fp.write("# element %r as %s\n" % (code, keyform))
1015 def fillclasspropsandelems(self, cls):
1016 [name, code, desc, properties, elements] = cls
1017 cname = identify(name)
1018 if self.namemappers[0].hascode('class', code) and \
1019 self.namemappers[0].findcodename('class', code)[0] != cname:
1020 # This is an other name (plural or so) for something else. Skip.
1021 if self.fp and (elements or len(properties) > 1 or (len(properties) == 1 and
1022 properties[0][1] != 'c@#!')):
1023 if self.verbose:
1024 print >>self.verbose, '** Skip multiple %s of %s (code %r)' % (cname, self.namemappers[0].findcodename('class', code)[0], code)
1025 raise RuntimeError, "About to skip non-empty class"
1026 return
1027 plist = []
1028 elist = []
1029 superclasses = []
1030 for prop in properties:
1031 [pname, pcode, what] = prop
1032 if pcode == "c@#^":
1033 superclasses.append(what)
1034 if pcode == 'c@#!':
1035 continue
1036 pname = identify(pname)
1037 plist.append(pname)
1039 superclassnames = []
1040 for superclass in superclasses:
1041 superId, superDesc, dummy = superclass
1042 superclassname, fullyqualifiedname, module = self.findcodename("class", superId)
1043 # I don't think this is correct:
1044 if superclassname == cname:
1045 pass # superclassnames.append(fullyqualifiedname)
1046 else:
1047 superclassnames.append(superclassname)
1049 if self.fp:
1050 self.fp.write("%s._superclassnames = %r\n"%(cname, superclassnames))
1052 for elem in elements:
1053 [ecode, keyform] = elem
1054 if ecode == 'c@#!':
1055 continue
1056 name, ename, module = self.findcodename('class', ecode)
1057 if not name:
1058 if self.fp:
1059 self.fp.write("# XXXX %s element %r not found!!\n"%(cname, ecode))
1060 else:
1061 elist.append((name, ename))
1063 plist.sort()
1064 elist.sort()
1066 if self.fp:
1067 self.fp.write("%s._privpropdict = {\n"%cname)
1068 for n in plist:
1069 self.fp.write(" '%s' : _Prop_%s,\n"%(n, n))
1070 self.fp.write("}\n")
1071 self.fp.write("%s._privelemdict = {\n"%cname)
1072 for n, fulln in elist:
1073 self.fp.write(" '%s' : %s,\n"%(n, fulln))
1074 self.fp.write("}\n")
1076 def compilecomparison(self, comp):
1077 [name, code, comment] = comp
1078 iname = identify(name)
1079 self.namemappers[0].addnamecode('comparison', iname, code)
1080 if self.fp:
1081 self.fp.write("class %s(aetools.NComparison):\n" % iname)
1082 self.fp.write(' """%s - %s """\n' % (ascii(name), ascii(comment)))
1084 def compileenumeration(self, enum):
1085 [code, items] = enum
1086 name = "_Enum_%s" % identify(code)
1087 if self.fp:
1088 self.fp.write("%s = {\n" % name)
1089 for item in items:
1090 self.compileenumerator(item)
1091 self.fp.write("}\n\n")
1092 self.namemappers[0].addnamecode('enum', name, code)
1093 return code
1095 def compileenumerator(self, item):
1096 [name, code, desc] = item
1097 self.fp.write(" %r : %r,\t# %s\n" % (identify(name), code, ascii(desc)))
1099 def checkforenum(self, enum):
1100 """This enum code is used by an event. Make sure it's available"""
1101 name, fullname, module = self.findcodename('enum', enum)
1102 if not name:
1103 if self.fp:
1104 self.fp.write("_Enum_%s = None # XXXX enum %s not found!!\n"%(identify(enum), ascii(enum)))
1105 return
1106 if module:
1107 if self.fp:
1108 self.fp.write("from %s import %s\n"%(module, name))
1110 def dumpindex(self):
1111 if not self.fp:
1112 return
1113 self.fp.write("\n#\n# Indices of types declared in this module\n#\n")
1115 self.fp.write("_classdeclarations = {\n")
1116 classlist = self.namemappers[0].getall('class')
1117 classlist.sort()
1118 for k, v in classlist:
1119 self.fp.write(" %r : %s,\n" % (k, v))
1120 self.fp.write("}\n")
1122 self.fp.write("\n_propdeclarations = {\n")
1123 proplist = self.namemappers[0].getall('property')
1124 proplist.sort()
1125 for k, v in proplist:
1126 self.fp.write(" %r : _Prop_%s,\n" % (k, v))
1127 self.fp.write("}\n")
1129 self.fp.write("\n_compdeclarations = {\n")
1130 complist = self.namemappers[0].getall('comparison')
1131 complist.sort()
1132 for k, v in complist:
1133 self.fp.write(" %r : %s,\n" % (k, v))
1134 self.fp.write("}\n")
1136 self.fp.write("\n_enumdeclarations = {\n")
1137 enumlist = self.namemappers[0].getall('enum')
1138 enumlist.sort()
1139 for k, v in enumlist:
1140 self.fp.write(" %r : %s,\n" % (k, v))
1141 self.fp.write("}\n")
1143 def compiledata(data):
1144 [type, description, flags] = data
1145 return "%r -- %r %s" % (type, description, compiledataflags(flags))
1147 def is_null(data):
1148 return data[0] == 'null'
1150 def is_optional(data):
1151 return (data[2] & 0x8000)
1153 def is_enum(data):
1154 return (data[2] & 0x2000)
1156 def getdatadoc(data):
1157 [type, descr, flags] = data
1158 if descr:
1159 return ascii(descr)
1160 if type == '****':
1161 return 'anything'
1162 if type == 'obj ':
1163 return 'an AE object reference'
1164 return "undocumented, typecode %r"%(type,)
1166 dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
1167 def compiledataflags(flags):
1168 bits = []
1169 for i in range(16):
1170 if flags & (1<<i):
1171 if i in dataflagdict.keys():
1172 bits.append(dataflagdict[i])
1173 else:
1174 bits.append(repr(i))
1175 return '[%s]' % string.join(bits)
1177 def ascii(str):
1178 """Return a string with all non-ascii characters hex-encoded"""
1179 if type(str) != type(''):
1180 return map(ascii, str)
1181 rv = ''
1182 for c in str:
1183 if c in ('\t', '\n', '\r') or ' ' <= c < chr(0x7f):
1184 rv = rv + c
1185 else:
1186 rv = rv + '\\' + 'x%02.2x' % ord(c)
1187 return rv
1189 def identify(str):
1190 """Turn any string into an identifier:
1191 - replace space by _
1192 - replace other illegal chars by _xx_ (hex code)
1193 - append _ if the result is a python keyword
1195 if not str:
1196 return "empty_ae_name_"
1197 rv = ''
1198 ok = string.ascii_letters + '_'
1199 ok2 = ok + string.digits
1200 for c in str:
1201 if c in ok:
1202 rv = rv + c
1203 elif c == ' ':
1204 rv = rv + '_'
1205 else:
1206 rv = rv + '_%02.2x_'%ord(c)
1207 ok = ok2
1208 if keyword.iskeyword(rv):
1209 rv = rv + '_'
1210 return rv
1212 # Call the main program
1214 if __name__ == '__main__':
1215 main()
1216 sys.exit(1)