1 """ 2 gensuitemodule - Generate an AE suite module from an aete/aeut resource 3 4 Based on aete.py. 5 6 Reading and understanding this code is left as an exercise to the reader. 7 """ 8 9 from warnings import warnpy3k 10 warnpy3k("In 3.x, the gensuitemodule module is removed.", stacklevel=2) 11 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 29 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() 33 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) 46 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() 55 56 process_func = processfile 57 basepkgname = 'StdSuites' 58 output = None 59 edit_modnames = [] 60 creatorsignature = None 61 dump = None 62 verbose = None 63 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 85 86 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) 90 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() 97 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) 125 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 146 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) 185 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) 226 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 257 258 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) 265 266 def dumpaetelist(aetelist, output): 267 import pprint 268 pprint.pprint(aetelist, output) 269 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 282 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 291 292 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. 296 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) 302 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]) 309 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]) 316 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 323 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 334 335 def getalign(f): 336 if f.tell() & 1: 337 c = f.read(1) 338 ##if c != '\0': 339 ## print align:', repr(c) 340 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 348 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 354 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,) 365 366 getdata = [ 367 (getostype, "type"), 368 (getpstr, "description"), 369 (getword, "flags") 370 ] 371 getargument = [ 372 (getpstr, "name"), 373 (getostype, "keyword"), 374 (getdata, "what") 375 ] 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) 384 ] 385 getproperty = [ 386 (getpstr, "name"), 387 (getostype, "code"), 388 (getdata, "what") 389 ] 390 getelement = [ 391 (getostype, "type"), 392 (getlist, "keyform", getostype) 393 ] 394 getclass = [ 395 (getpstr, "name"), 396 (getostype, "class code"), 397 (getpstr, "description"), 398 (getlist, "properties", getproperty), 399 (getlist, "elements", getelement) 400 ] 401 getcomparison = [ 402 (getpstr, "operator name"), 403 (getostype, "operator ID"), 404 (getpstr, "operator comment"), 405 ] 406 getenumerator = [ 407 (getpstr, "enumerator name"), 408 (getostype, "enumerator ID"), 409 (getpstr, "enumerator comment") 410 ] 411 getenumeration = [ 412 (getostype, "enumeration ID"), 413 (getlist, "enumerator", getenumerator) 414 ] 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) 425 ] 426 getaete = [ 427 (getword, "major/minor version in BCD"), 428 (getword, "language code"), 429 (getword, "script code"), 430 (getlist, "suites", getsuite) 431 ] 432 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) 506 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) 527 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") 539 540 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() 552 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 560 561 # Set by precompilesuite 562 self.pathname = None 563 self.modname = None 564 565 # Set by compilesuite 566 self.fp = None 567 self.basemodule = None 568 self.enumsneeded = {} 569 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 575 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 592 593 self.modname = os.path.splitext(os.path.split(self.pathname)[1])[0] 594 595 if self.basepackage and code in self.basepackage._code_to_module: 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 602 603 self.enumsneeded = {} 604 for event in events: 605 self.findenumsinevent(event) 606 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) 617 618 for enum in self.enumsneeded.keys(): 619 objc.checkforenum(enum) 620 621 objc.dumpindex() 622 623 precompinfo = objc.getprecompinfo(self.modname) 624 625 return code, self.modname, precompinfo 626 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 643 644 events.sort() 645 classes.sort(class_sorter) 646 comps.sort() 647 enums.sort() 648 649 self.fp = fp = open(self.pathname, 'w') 650 MacOS.SetCreatorAndType(self.pathname, 'Pyth', 'TEXT') 651 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') 658 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 code in self.basepackage._code_to_module: 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 code.lower() in self.basepackage._code_to_module: 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() 676 677 self.enumsneeded = {} 678 if events: 679 for event in events: 680 self.compileevent(event) 681 else: 682 fp.write(" pass\n\n") 683 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) 694 695 for enum in self.enumsneeded.keys(): 696 objc.checkforenum(enum) 697 698 objc.dumpindex() 699 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) 709 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) 715 # 716 # generate name->keyword map 717 # 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") 723 724 # 725 # Generate function header 726 # 727 has_arg = (not is_null(accepts)) 728 opt_arg = (has_arg and is_optional(accepts)) 729 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 739 # 740 # Generate doc string (important, since it may be the only 741 # available documentation, due to our name-remaping) 742 # 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') 755 # 756 # Fiddle the args so everything ends up in 'arguments' dictionary 757 # 758 fp.write(" _code = %r\n"% (code,)) 759 fp.write(" _subcode = %r\n\n"% (subcode,)) 760 # 761 # Do keyword name substitution 762 # 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") 767 # 768 # Stuff required arg (if there is one) into arguments 769 # 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") 778 # 779 # Do enum-name substitution 780 # 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") 790 # 791 # Do the transaction 792 # 793 fp.write(" _reply, _arguments, _attributes = self.send(_code, _subcode,\n") 794 fp.write(" _arguments, _attributes)\n") 795 # 796 # Error handling 797 # 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") 801 # 802 # Decode result 803 # 804 fp.write(" if '----' in _arguments:\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") 809 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 818 819 # 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. 825 # 826 class CodeNameMapper: 827 828 def __init__(self, interact=1, verbose=None): 829 self.code2name = { 830 "property" : {}, 831 "class" : {}, 832 "enum" : {}, 833 "comparison" : {}, 834 } 835 self.name2code = { 836 "property" : {}, 837 "class" : {}, 838 "enum" : {}, 839 "comparison" : {}, 840 } 841 self.modulename = None 842 self.star_imported = 0 843 self.can_interact = interact 844 self.verbose = verbose 845 846 def addnamecode(self, type, name, code): 847 self.name2code[type][name] = code 848 if code not in self.code2name[type]: 849 self.code2name[type][code] = name 850 851 def hasname(self, name): 852 for dict in self.name2code.values(): 853 if name in dict: 854 return True 855 return False 856 857 def hascode(self, type, code): 858 return code in self.code2name[type] 859 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 869 870 def getall(self, type): 871 return self.code2name[type].items() 872 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) 884 885 def prepareforexport(self, name=None): 886 if not self.modulename: 887 self.modulename = name 888 return self 889 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) 907 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 915 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) 941 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 949 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 964 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) 986 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)) 1009 1010 def compileelement(self, elem): 1011 [code, keyform] = elem 1012 if self.fp: 1013 self.fp.write("# element %r as %s\n" % (code, keyform)) 1014 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) 1038 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) 1048 1049 if self.fp: 1050 self.fp.write("%s._superclassnames = %r\n"%(cname, superclassnames)) 1051 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)) 1062 1063 plist.sort() 1064 elist.sort() 1065 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") 1075 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))) 1083 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 1094 1095 def compileenumerator(self, item): 1096 [name, code, desc] = item 1097 self.fp.write(" %r : %r,\t# %s\n" % (identify(name), code, ascii(desc))) 1098 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)) 1109 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") 1114 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") 1121 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") 1128 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") 1135 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") 1142 1143 def compiledata(data): 1144 [type, description, flags] = data 1145 return "%r -- %r %s" % (type, description, compiledataflags(flags)) 1146 1147 def is_null(data): 1148 return data[0] == 'null' 1149 1150 def is_optional(data): 1151 return (data[2] & 0x8000) 1152 1153 def is_enum(data): 1154 return (data[2] & 0x2000) 1155 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,) 1165 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) 1176 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 1188 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 1194 """ 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 1211 1212 # Call the main program 1213 1214 if __name__ == '__main__': 1215 main() 1216 sys.exit(1) 1217