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