Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 1 | """ |
| 2 | gensuitemodule - Generate an AE suite module from an aete/aeut resource |
| 3 | |
| 4 | Based on aete.py |
| 5 | """ |
| 6 | |
| 7 | import MacOS |
| 8 | import os |
| 9 | import string |
| 10 | import sys |
| 11 | import types |
| 12 | import StringIO |
| 13 | import macfs |
| 14 | |
| 15 | from Res import * |
| 16 | |
| 17 | def main(): |
Jack Jansen | 9d19a91 | 1995-08-14 12:14:55 +0000 | [diff] [blame] | 18 | fss, ok = macfs.PromptGetFile('Select file with aeut/aete resource:') |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 19 | if not ok: |
| 20 | sys.exit(0) |
| 21 | process(fss.as_pathname()) |
| 22 | |
| 23 | def process(fullname): |
| 24 | """Process all resources in a single file""" |
| 25 | cur = CurResFile() |
| 26 | print fullname |
| 27 | rf = OpenRFPerm(fullname, 0, 1) |
| 28 | try: |
| 29 | UseResFile(rf) |
| 30 | resources = [] |
| 31 | for i in range(Count1Resources('aete')): |
| 32 | res = Get1IndResource('aete', 1+i) |
| 33 | resources.append(res) |
| 34 | for i in range(Count1Resources('aeut')): |
| 35 | res = Get1IndResource('aeut', 1+i) |
| 36 | resources.append(res) |
| 37 | print "\nLISTING aete+aeut RESOURCES IN", `fullname` |
| 38 | for res in resources: |
| 39 | print "decoding", res.GetResInfo(), "..." |
| 40 | data = res.data |
| 41 | aete = decode(data) |
| 42 | compileaete(aete, fullname) |
| 43 | finally: |
| 44 | if rf <> cur: |
| 45 | CloseResFile(rf) |
| 46 | UseResFile(cur) |
| 47 | |
| 48 | def decode(data): |
| 49 | """Decode a resource into a python data structure""" |
| 50 | f = StringIO.StringIO(data) |
| 51 | aete = generic(getaete, f) |
| 52 | aete = simplify(aete) |
| 53 | processed = f.tell() |
| 54 | unprocessed = len(f.read()) |
| 55 | total = f.tell() |
| 56 | if unprocessed: |
| 57 | sys.stderr.write("%d processed + %d unprocessed = %d total\n" % |
| 58 | (processed, unprocessed, total)) |
| 59 | return aete |
| 60 | |
| 61 | def simplify(item): |
| 62 | """Recursively replace singleton tuples by their constituent item""" |
| 63 | if type(item) is types.ListType: |
| 64 | return map(simplify, item) |
| 65 | elif type(item) == types.TupleType and len(item) == 2: |
| 66 | return simplify(item[1]) |
| 67 | else: |
| 68 | return item |
| 69 | |
| 70 | |
| 71 | # Here follows the aete resource decoder. |
| 72 | # It is presented bottom-up instead of top-down because there are direct |
| 73 | # references to the lower-level part-decoders from the high-level part-decoders. |
| 74 | |
| 75 | def getbyte(f, *args): |
| 76 | c = f.read(1) |
| 77 | if not c: |
| 78 | raise EOFError, 'in getbyte' + str(args) |
| 79 | return ord(c) |
| 80 | |
| 81 | def getword(f, *args): |
| 82 | getalign(f) |
| 83 | s = f.read(2) |
| 84 | if len(s) < 2: |
| 85 | raise EOFError, 'in getword' + str(args) |
| 86 | return (ord(s[0])<<8) | ord(s[1]) |
| 87 | |
| 88 | def getlong(f, *args): |
| 89 | getalign(f) |
| 90 | s = f.read(4) |
| 91 | if len(s) < 4: |
| 92 | raise EOFError, 'in getlong' + str(args) |
| 93 | return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3]) |
| 94 | |
| 95 | def getostype(f, *args): |
| 96 | getalign(f) |
| 97 | s = f.read(4) |
| 98 | if len(s) < 4: |
| 99 | raise EOFError, 'in getostype' + str(args) |
| 100 | return s |
| 101 | |
| 102 | def getpstr(f, *args): |
| 103 | c = f.read(1) |
| 104 | if len(c) < 1: |
| 105 | raise EOFError, 'in getpstr[1]' + str(args) |
| 106 | nbytes = ord(c) |
| 107 | if nbytes == 0: return '' |
| 108 | s = f.read(nbytes) |
| 109 | if len(s) < nbytes: |
| 110 | raise EOFError, 'in getpstr[2]' + str(args) |
| 111 | return s |
| 112 | |
| 113 | def getalign(f): |
| 114 | if f.tell() & 1: |
| 115 | c = f.read(1) |
| 116 | ##if c <> '\0': |
| 117 | ## print 'align:', `c` |
| 118 | |
| 119 | def getlist(f, description, getitem): |
| 120 | count = getword(f) |
| 121 | list = [] |
| 122 | for i in range(count): |
| 123 | list.append(generic(getitem, f)) |
| 124 | getalign(f) |
| 125 | return list |
| 126 | |
| 127 | def alt_generic(what, f, *args): |
| 128 | print "generic", `what`, args |
| 129 | res = vageneric(what, f, args) |
| 130 | print '->', `res` |
| 131 | return res |
| 132 | |
| 133 | def generic(what, f, *args): |
| 134 | if type(what) == types.FunctionType: |
| 135 | return apply(what, (f,) + args) |
| 136 | if type(what) == types.ListType: |
| 137 | record = [] |
| 138 | for thing in what: |
| 139 | item = apply(generic, thing[:1] + (f,) + thing[1:]) |
| 140 | record.append((thing[1], item)) |
| 141 | return record |
| 142 | return "BAD GENERIC ARGS: %s" % `what` |
| 143 | |
| 144 | getdata = [ |
| 145 | (getostype, "type"), |
| 146 | (getpstr, "description"), |
| 147 | (getword, "flags") |
| 148 | ] |
| 149 | getargument = [ |
| 150 | (getpstr, "name"), |
| 151 | (getostype, "keyword"), |
| 152 | (getdata, "what") |
| 153 | ] |
| 154 | getevent = [ |
| 155 | (getpstr, "name"), |
| 156 | (getpstr, "description"), |
| 157 | (getostype, "suite code"), |
| 158 | (getostype, "event code"), |
| 159 | (getdata, "returns"), |
| 160 | (getdata, "accepts"), |
| 161 | (getlist, "optional arguments", getargument) |
| 162 | ] |
| 163 | getproperty = [ |
| 164 | (getpstr, "name"), |
| 165 | (getostype, "code"), |
| 166 | (getdata, "what") |
| 167 | ] |
| 168 | getelement = [ |
| 169 | (getostype, "type"), |
| 170 | (getlist, "keyform", getostype) |
| 171 | ] |
| 172 | getclass = [ |
| 173 | (getpstr, "name"), |
| 174 | (getostype, "class code"), |
| 175 | (getpstr, "description"), |
| 176 | (getlist, "properties", getproperty), |
| 177 | (getlist, "elements", getelement) |
| 178 | ] |
| 179 | getcomparison = [ |
| 180 | (getpstr, "operator name"), |
| 181 | (getostype, "operator ID"), |
| 182 | (getpstr, "operator comment"), |
| 183 | ] |
| 184 | getenumerator = [ |
| 185 | (getpstr, "enumerator name"), |
| 186 | (getostype, "enumerator ID"), |
| 187 | (getpstr, "enumerator comment") |
| 188 | ] |
| 189 | getenumeration = [ |
| 190 | (getostype, "enumeration ID"), |
| 191 | (getlist, "enumerator", getenumerator) |
| 192 | ] |
| 193 | getsuite = [ |
| 194 | (getpstr, "suite name"), |
| 195 | (getpstr, "suite description"), |
| 196 | (getostype, "suite ID"), |
| 197 | (getword, "suite level"), |
| 198 | (getword, "suite version"), |
| 199 | (getlist, "events", getevent), |
| 200 | (getlist, "classes", getclass), |
| 201 | (getlist, "comparisons", getcomparison), |
| 202 | (getlist, "enumerations", getenumeration) |
| 203 | ] |
| 204 | getaete = [ |
| 205 | (getword, "major/minor version in BCD"), |
| 206 | (getword, "language code"), |
| 207 | (getword, "script code"), |
| 208 | (getlist, "suites", getsuite) |
| 209 | ] |
| 210 | |
| 211 | def compileaete(aete, fname): |
| 212 | """Generate code for a full aete resource. fname passed for doc purposes""" |
| 213 | [version, language, script, suites] = aete |
| 214 | major, minor = divmod(version, 256) |
| 215 | for suite in suites: |
| 216 | compilesuite(suite, major, minor, language, script, fname) |
| 217 | |
| 218 | def compilesuite(suite, major, minor, language, script, fname): |
| 219 | """Generate code for a single suite""" |
| 220 | [name, desc, code, level, version, events, classes, comps, enums] = suite |
| 221 | |
| 222 | modname = identify(name) |
| 223 | fss, ok = macfs.StandardPutFile('Python output file', modname+'.py') |
| 224 | if not ok: |
| 225 | return |
| 226 | fp = open(fss.as_pathname(), 'w') |
Jack Jansen | 18a99f5 | 1996-03-18 13:35:00 +0000 | [diff] [blame] | 227 | fss.SetCreatorType('Pyth', 'TEXT') |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 228 | |
| 229 | fp.write('"""Suite %s: %s\n' % (name, desc)) |
| 230 | fp.write("Level %d, version %d\n\n" % (level, version)) |
| 231 | fp.write("Generated from %s\n"%fname) |
| 232 | fp.write("AETE/AEUT resource version %d/%d, language %d, script %d\n" % \ |
| 233 | (major, minor, language, script)) |
| 234 | fp.write('"""\n\n') |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 235 | |
| 236 | fp.write('import aetools\n') |
| 237 | fp.write('import MacOS\n\n') |
| 238 | fp.write("_code = %s\n\n"% `code`) |
| 239 | |
| 240 | enum_names = [] |
| 241 | for enum in enums: |
| 242 | name = compileenumeration(fp, enum) |
| 243 | enum_names.append(enum) |
| 244 | |
| 245 | compileclassheader(fp, modname) |
| 246 | if events: |
| 247 | for event in events: |
| 248 | compileevent(fp, event) |
| 249 | else: |
| 250 | fp.write("\tpass\n\n") |
| 251 | for cls in classes: |
| 252 | compileclass(fp, cls) |
| 253 | for comp in comps: |
| 254 | compilecomparison(fp, comp) |
| 255 | |
| 256 | def compileclassheader(fp, name): |
| 257 | """Generate class boilerplate""" |
| 258 | fp.write("class %s:\n\n"%name) |
| 259 | |
| 260 | def compileevent(fp, event): |
| 261 | """Generate code for a single event""" |
| 262 | [name, desc, code, subcode, returns, accepts, arguments] = event |
| 263 | funcname = identify(name) |
| 264 | # |
| 265 | # generate name->keyword map |
| 266 | # |
| 267 | if arguments: |
| 268 | fp.write("\t_argmap_%s = {\n"%funcname) |
| 269 | for a in arguments: |
| 270 | fp.write("\t\t%s : %s,\n"%(`identify(a[0])`, `a[1]`)) |
| 271 | fp.write("\t}\n\n") |
| 272 | |
| 273 | # |
| 274 | # Generate function header |
| 275 | # |
| 276 | has_arg = (not is_null(accepts)) |
| 277 | opt_arg = (has_arg and is_optional(accepts)) |
| 278 | |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 279 | fp.write("\tdef %s(self, "%funcname) |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 280 | if has_arg: |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 281 | if not opt_arg: |
| 282 | fp.write("_object, ") # Include direct object, if it has one |
| 283 | else: |
| 284 | fp.write("_object=None, ") # Also include if it is optional |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 285 | else: |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 286 | fp.write("_no_object=None, ") # For argument checking |
| 287 | fp.write("_attributes={}, **_arguments):\n") # include attribute dict and args |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 288 | # |
| 289 | # Generate doc string (important, since it may be the only |
| 290 | # available documentation, due to our name-remaping) |
| 291 | # |
| 292 | fp.write('\t\t"""%s: %s\n'%(name, desc)) |
| 293 | if has_arg: |
| 294 | fp.write("\t\tRequired argument: %s\n"%getdatadoc(accepts)) |
| 295 | elif opt_arg: |
| 296 | fp.write("\t\tOptional argument: %s\n"%getdatadoc(accepts)) |
| 297 | for arg in arguments: |
| 298 | fp.write("\t\tKeyword argument %s: %s\n"%(identify(arg[0]), |
| 299 | getdatadoc(arg[2]))) |
| 300 | fp.write("\t\tKeyword argument _attributes: AppleEvent attribute dictionary\n") |
| 301 | if not is_null(returns): |
| 302 | fp.write("\t\tReturns: %s\n"%getdatadoc(returns)) |
| 303 | fp.write('\t\t"""\n') |
| 304 | # |
| 305 | # Fiddle the args so everything ends up in 'arguments' dictionary |
| 306 | # |
| 307 | fp.write("\t\t_code = %s\n"% `code`) |
| 308 | fp.write("\t\t_subcode = %s\n\n"% `subcode`) |
Jack Jansen | 7321514 | 1995-10-09 23:09:23 +0000 | [diff] [blame] | 309 | # |
| 310 | # Do keyword name substitution |
| 311 | # |
| 312 | if arguments: |
| 313 | fp.write("\t\taetools.keysubst(_arguments, self._argmap_%s)\n"%funcname) |
| 314 | else: |
| 315 | fp.write("\t\tif _arguments: raise TypeError, 'No optional args expected'\n") |
| 316 | # |
| 317 | # Stuff required arg (if there is one) into arguments |
| 318 | # |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 319 | if has_arg: |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 320 | fp.write("\t\t_arguments['----'] = _object\n") |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 321 | elif opt_arg: |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 322 | fp.write("\t\tif _object:\n") |
| 323 | fp.write("\t\t\t_arguments['----'] = _object\n") |
| 324 | else: |
| 325 | fp.write("\t\tif _no_object != None: raise TypeError, 'No direct arg expected'\n") |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 326 | fp.write("\n") |
| 327 | # |
Jack Jansen | 7321514 | 1995-10-09 23:09:23 +0000 | [diff] [blame] | 328 | # Do enum-name substitution |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 329 | # |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 330 | for a in arguments: |
| 331 | if is_enum(a[2]): |
| 332 | kname = a[1] |
| 333 | ename = a[2][0] |
Jack Jansen | a2408e9 | 1996-04-16 14:36:46 +0000 | [diff] [blame] | 334 | if ename <> '****': |
| 335 | fp.write("\t\taetools.enumsubst(_arguments, %s, _Enum_%s)\n" % |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 336 | (`kname`, ename)) |
| 337 | fp.write("\n") |
| 338 | # |
| 339 | # Do the transaction |
| 340 | # |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 341 | fp.write("\t\t_reply, _arguments, _attributes = self.send(_code, _subcode,\n") |
| 342 | fp.write("\t\t\t\t_arguments, _attributes)\n") |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 343 | # |
| 344 | # Error handling |
| 345 | # |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 346 | fp.write("\t\tif _arguments.has_key('errn'):\n") |
| 347 | fp.write("\t\t\traise MacOS.Error, aetools.decodeerror(_arguments)\n") |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 348 | fp.write("\t\t# XXXX Optionally decode result\n") |
| 349 | # |
| 350 | # Decode result |
| 351 | # |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 352 | fp.write("\t\tif _arguments.has_key('----'):\n") |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 353 | if is_enum(returns): |
| 354 | fp.write("\t\t\t# XXXX Should do enum remapping here...\n") |
Jack Jansen | 8426477 | 1995-10-03 14:35:58 +0000 | [diff] [blame] | 355 | fp.write("\t\t\treturn _arguments['----']\n") |
Jack Jansen | 5ccd826 | 1995-07-17 11:43:20 +0000 | [diff] [blame] | 356 | fp.write("\n") |
| 357 | |
| 358 | # print "\n# Command %s -- %s (%s, %s)" % (`name`, `desc`, `code`, `subcode`) |
| 359 | # print "# returns", compiledata(returns) |
| 360 | # print "# accepts", compiledata(accepts) |
| 361 | # for arg in arguments: |
| 362 | # compileargument(arg) |
| 363 | |
| 364 | def compileargument(arg): |
| 365 | [name, keyword, what] = arg |
| 366 | print "# %s (%s)" % (name, `keyword`), compiledata(what) |
| 367 | |
| 368 | def compileclass(fp, cls): |
| 369 | [name, code, desc, properties, elements] = cls |
| 370 | fp.write("\n# Class %s (%s) -- %s\n" % (`name`, `code`, `desc`)) |
| 371 | for prop in properties: |
| 372 | compileproperty(fp, prop) |
| 373 | for elem in elements: |
| 374 | compileelement(fp, elem) |
| 375 | |
| 376 | def compileproperty(fp, prop): |
| 377 | [name, code, what] = prop |
| 378 | fp.write("# property %s (%s) %s\n" % (`name`, `code`, compiledata(what))) |
| 379 | |
| 380 | def compileelement(fp, elem): |
| 381 | [code, keyform] = elem |
| 382 | fp.write("# element %s as %s\n" % (`code`, keyform)) |
| 383 | |
| 384 | def compilecomparison(fp, comp): |
| 385 | [name, code, comment] = comp |
| 386 | fp.write("# comparison %s (%s) -- %s\n" % (`name`, `code`, comment)) |
| 387 | |
| 388 | def compileenumeration(fp, enum): |
| 389 | [code, items] = enum |
| 390 | fp.write("_Enum_%s = {\n" % identify(code)) |
| 391 | for item in items: |
| 392 | compileenumerator(fp, item) |
| 393 | fp.write("}\n\n") |
| 394 | return code |
| 395 | |
| 396 | def compileenumerator(fp, item): |
| 397 | [name, code, desc] = item |
| 398 | fp.write("\t%s : %s,\t# %s\n" % (`identify(name)`, `code`, desc)) |
| 399 | |
| 400 | def compiledata(data): |
| 401 | [type, description, flags] = data |
| 402 | return "%s -- %s %s" % (`type`, `description`, compiledataflags(flags)) |
| 403 | |
| 404 | def is_null(data): |
| 405 | return data[0] == 'null' |
| 406 | |
| 407 | def is_optional(data): |
| 408 | return (data[2] & 0x8000) |
| 409 | |
| 410 | def is_enum(data): |
| 411 | return (data[2] & 0x2000) |
| 412 | |
| 413 | def getdatadoc(data): |
| 414 | [type, descr, flags] = data |
| 415 | if descr: |
| 416 | return descr |
| 417 | if type == '****': |
| 418 | return 'anything' |
| 419 | if type == 'obj ': |
| 420 | return 'an AE object reference' |
| 421 | return "undocumented, typecode %s"%`type` |
| 422 | |
| 423 | dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"} |
| 424 | def compiledataflags(flags): |
| 425 | bits = [] |
| 426 | for i in range(16): |
| 427 | if flags & (1<<i): |
| 428 | if i in dataflagdict.keys(): |
| 429 | bits.append(dataflagdict[i]) |
| 430 | else: |
| 431 | bits.append(`i`) |
| 432 | return '[%s]' % string.join(bits) |
| 433 | |
| 434 | # XXXX Do we have a set of python keywords somewhere? |
| 435 | illegal_ids = [ "for", "in", "from", "and", "or", "not", "print" ] |
| 436 | |
| 437 | def identify(str): |
| 438 | """Turn any string into an identifier: |
| 439 | - replace space by _ |
| 440 | - replace other illegal chars by _xx_ (hex code) |
| 441 | - prepend _ if the result is a python keyword |
| 442 | """ |
| 443 | rv = '' |
| 444 | ok = string.letters + '_' |
| 445 | ok2 = ok + string.digits |
| 446 | for c in str: |
| 447 | if c in ok: |
| 448 | rv = rv + c |
| 449 | elif c == ' ': |
| 450 | rv = rv + '_' |
| 451 | else: |
| 452 | rv = rv + '_%02.2x_'%ord(c) |
| 453 | ok = ok2 |
| 454 | if rv in illegal_ids: |
| 455 | rv = '_' + rv |
| 456 | return rv |
| 457 | |
| 458 | # Call the main program |
| 459 | |
| 460 | if __name__ == '__main__': |
| 461 | main() |
| 462 | sys.exit(1) |