gensuitemodule.py 26.4 KB
Newer Older
1 2 3
"""
gensuitemodule - Generate an AE suite module from an aete/aeut resource

4 5 6
Based on aete.py.

Reading and understanding this code is left as an exercise to the reader.
7 8 9 10 11 12 13 14 15
"""

import MacOS
import os
import string
import sys
import types
import StringIO
import macfs
16
import keyword
17
import macresource
18
from aetools import unpack
19

20
from Carbon.Res import *
21

22 23
DEFAULT_PACKAGEFOLDER=os.path.join(sys.prefix, 'Mac', 'Lib', 'lib-scriptpackages')

24
def main():
25 26 27 28 29 30 31 32
	if len(sys.argv) > 1:
		for filename in sys.argv[1:]:
			processfile(filename)
	else:
		fss, ok = macfs.PromptGetFile('Select file with aeut/aete resource:')
		if not ok:
			sys.exit(0)
		processfile(fss.as_pathname())
33

34
def processfile(fullname):
35 36
	"""Process all resources in a single file"""
	cur = CurResFile()
37 38
	print "Processing", fullname
	rf = macresource.open_pathname(fullname)
39 40 41 42 43 44 45 46 47 48
	try:
		UseResFile(rf)
		resources = []
		for i in range(Count1Resources('aete')):
			res = Get1IndResource('aete', 1+i)
			resources.append(res)
		for i in range(Count1Resources('aeut')):
			res = Get1IndResource('aeut', 1+i)
			resources.append(res)
		print "\nLISTING aete+aeut RESOURCES IN", `fullname`
49
		aetelist = []
50 51 52 53
		for res in resources:
			print "decoding", res.GetResInfo(), "..."
			data = res.data
			aete = decode(data)
54
			aetelist.append((aete, res.GetResInfo()))
55 56 57 58
	finally:
		if rf <> cur:
			CloseResFile(rf)
			UseResFile(cur)
59 60 61
	# switch back (needed for dialogs in Python)
	UseResFile(cur)
	compileaetelist(aetelist, fullname)
62

63 64 65 66
def compileaetelist(aetelist, fullname):
	for aete, resinfo in aetelist:
		compileaete(aete, resinfo, fullname)
		
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
def decode(data):
	"""Decode a resource into a python data structure"""
	f = StringIO.StringIO(data)
	aete = generic(getaete, f)
	aete = simplify(aete)
	processed = f.tell()
	unprocessed = len(f.read())
	total = f.tell()
	if unprocessed:
		sys.stderr.write("%d processed + %d unprocessed = %d total\n" %
		                 (processed, unprocessed, total))
	return aete

def simplify(item):
	"""Recursively replace singleton tuples by their constituent item"""
	if type(item) is types.ListType:
		return map(simplify, item)
	elif type(item) == types.TupleType and len(item) == 2:
		return simplify(item[1])
	else:
		return item


# Here follows the aete resource decoder.
# It is presented bottom-up instead of top-down because there are  direct
# references to the lower-level part-decoders from the high-level part-decoders.

def getbyte(f, *args):
	c = f.read(1)
	if not c:
		raise EOFError, 'in getbyte' + str(args)
	return ord(c)

def getword(f, *args):
	getalign(f)
	s = f.read(2)
	if len(s) < 2:
		raise EOFError, 'in getword' + str(args)
	return (ord(s[0])<<8) | ord(s[1])

def getlong(f, *args):
	getalign(f)
	s = f.read(4)
	if len(s) < 4:
		raise EOFError, 'in getlong' + str(args)
	return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3])

def getostype(f, *args):
	getalign(f)
	s = f.read(4)
	if len(s) < 4:
		raise EOFError, 'in getostype' + str(args)
	return s

def getpstr(f, *args):
	c = f.read(1)
	if len(c) < 1:
		raise EOFError, 'in getpstr[1]' + str(args)
	nbytes = ord(c)
	if nbytes == 0: return ''
	s = f.read(nbytes)
	if len(s) < nbytes:
		raise EOFError, 'in getpstr[2]' + str(args)
	return s

def getalign(f):
	if f.tell() & 1:
		c = f.read(1)
		##if c <> '\0':
		##	print 'align:', `c`

def getlist(f, description, getitem):
	count = getword(f)
	list = []
	for i in range(count):
		list.append(generic(getitem, f))
		getalign(f)
	return list

def alt_generic(what, f, *args):
	print "generic", `what`, args
	res = vageneric(what, f, args)
	print '->', `res`
	return res

def generic(what, f, *args):
	if type(what) == types.FunctionType:
		return apply(what, (f,) + args)
	if type(what) == types.ListType:
		record = []
		for thing in what:
			item = apply(generic, thing[:1] + (f,) + thing[1:])
			record.append((thing[1], item))
		return record
	return "BAD GENERIC ARGS: %s" % `what`

getdata = [
	(getostype, "type"),
	(getpstr, "description"),
	(getword, "flags")
	]
getargument = [
	(getpstr, "name"),
	(getostype, "keyword"),
	(getdata, "what")
	]
getevent = [
	(getpstr, "name"),
	(getpstr, "description"),
	(getostype, "suite code"),
	(getostype, "event code"),
	(getdata, "returns"),
	(getdata, "accepts"),
	(getlist, "optional arguments", getargument)
	]
getproperty = [
	(getpstr, "name"),
	(getostype, "code"),
	(getdata, "what")
	]
getelement = [
	(getostype, "type"),
	(getlist, "keyform", getostype)
	]
getclass = [
	(getpstr, "name"),
	(getostype, "class code"),
	(getpstr, "description"),
	(getlist, "properties", getproperty),
	(getlist, "elements", getelement)
	]
getcomparison = [
	(getpstr, "operator name"),
	(getostype, "operator ID"),
	(getpstr, "operator comment"),
	]
getenumerator = [
	(getpstr, "enumerator name"),
	(getostype, "enumerator ID"),
	(getpstr, "enumerator comment")
	]
getenumeration = [
	(getostype, "enumeration ID"),
	(getlist, "enumerator", getenumerator)
	]
getsuite = [
	(getpstr, "suite name"),
	(getpstr, "suite description"),
	(getostype, "suite ID"),
	(getword, "suite level"),
	(getword, "suite version"),
	(getlist, "events", getevent),
	(getlist, "classes", getclass),
	(getlist, "comparisons", getcomparison),
	(getlist, "enumerations", getenumeration)
	]
getaete = [
	(getword, "major/minor version in BCD"),
	(getword, "language code"),
	(getword, "script code"),
	(getlist, "suites", getsuite)
	]

230
def compileaete(aete, resinfo, fname):
231 232 233
	"""Generate code for a full aete resource. fname passed for doc purposes"""
	[version, language, script, suites] = aete
	major, minor = divmod(version, 256)
234 235
	fss = macfs.FSSpec(fname)
	creatorsignature, dummy = fss.GetCreatorType()
236
	packagename = identify(os.path.splitext(os.path.basename(fname))[0])
237 238 239 240 241 242
	if language:
		packagename = packagename+'_lang%d'%language
	if script:
		packagename = packagename+'_script%d'%script
	if len(packagename) > 27:
		packagename = packagename[:27]
243 244
	macfs.SetFolder(DEFAULT_PACKAGEFOLDER)
	fss, ok = macfs.GetDirectory('Create and select package folder for %s'%packagename)
245 246 247 248 249 250 251 252 253 254 255 256 257 258
	if not ok:
		return
	pathname = fss.as_pathname()
	packagename = os.path.split(os.path.normpath(pathname))[1]
	fss, ok = macfs.GetDirectory('Package folder for base suite (usually StdSuites)')
	if ok:
		dirname, basepkgname = os.path.split(os.path.normpath(fss.as_pathname()))
		if not dirname in sys.path:
			sys.path.insert(0, dirname)
		basepackage = __import__(basepkgname)
	else:
		basepackage = None
	macfs.SetFolder(pathname)
	suitelist = []
259 260
	allprecompinfo = []
	allsuites = []
261
	for suite in suites:
262 263 264 265 266 267 268 269 270
		code, suite, fss, modname, precompinfo = precompilesuite(suite, basepackage)
		if not code:
			continue
		allprecompinfo = allprecompinfo + precompinfo
		suiteinfo = suite, fss, modname
		suitelist.append((code, modname))
		allsuites.append(suiteinfo)
	for suiteinfo in allsuites:
		compilesuite(suiteinfo, major, minor, language, script, fname, basepackage, allprecompinfo)
271 272 273 274 275 276 277
	fss, ok = macfs.StandardPutFile('Package module', '__init__.py')
	if not ok:
		return
	fp = open(fss.as_pathname(), 'w')
	fss.SetCreatorType('Pyth', 'TEXT')
	fp.write('"""\n')
	fp.write("Package generated from %s\n"%fname)
278 279
	if resinfo:
		fp.write("Resource %s resid %d %s\n"%(ascii(resinfo[1]), resinfo[0], ascii(resinfo[2])))
280 281
	fp.write('"""\n')
	fp.write('import aetools\n')
Jack Jansen's avatar
Jack Jansen committed
282
	fp.write('Error = aetools.Error\n')
283 284 285 286
	for code, modname in suitelist:
		fp.write("import %s\n" % modname)
	fp.write("\n\n_code_to_module = {\n")
	for code, modname in suitelist:
287
		fp.write("\t'%s' : %s,\n"%(ascii(code), modname))
288 289 290
	fp.write("}\n\n")
	fp.write("\n\n_code_to_fullname = {\n")
	for code, modname in suitelist:
291
		fp.write("\t'%s' : ('%s.%s', '%s'),\n"%(ascii(code), packagename, modname, modname))
292 293 294
	fp.write("}\n\n")
	for code, modname in suitelist:
		fp.write("from %s import *\n"%modname)
295 296 297
	
	# Generate property dicts and element dicts for all types declared in this module
	fp.write("def getbaseclasses(v):\n")
298
	fp.write("\tif hasattr(v, '_superclassnames') and not hasattr(v, '_propdict'):\n")
299 300 301
	fp.write("\t\tv._propdict = {}\n")
	fp.write("\t\tv._elemdict = {}\n")
	fp.write("\t\tfor superclass in v._superclassnames:\n")
302 303 304
##	fp.write("\t\t\tgetbaseclasses(superclass)\n")
	fp.write("\t\t\tv._propdict.update(getattr(eval(superclass), '_privpropdict', {}))\n")
	fp.write("\t\t\tv._elemdict.update(getattr(eval(superclass), '_privelemdict', {}))\n")
305 306
	fp.write("\t\tv._propdict.update(v._privpropdict)\n")
	fp.write("\t\tv._elemdict.update(v._privelemdict)\n")
307
	fp.write("\n")
308 309 310 311 312 313 314 315 316 317 318 319 320
	fp.write("import StdSuites\n")
	if allprecompinfo:
		fp.write("\n#\n# Set property and element dictionaries now that all classes have been defined\n#\n")
		for codenamemapper in allprecompinfo:
			for k, v in codenamemapper.getall('class'):
				fp.write("getbaseclasses(%s)\n" % v)

	# Generate a code-to-name mapper for all of the types (classes) declared in this module
	if allprecompinfo:
		fp.write("\n#\n# Indices of types declared in this module\n#\n")
		fp.write("_classdeclarations = {\n")
		for codenamemapper in allprecompinfo:
			for k, v in codenamemapper.getall('class'):
321
				fp.write("\t%s : %s,\n" % (`k`, v))
322 323
		fp.write("}\n")

324 325 326
	if suitelist:
		fp.write("\n\nclass %s(%s_Events"%(packagename, suitelist[0][1]))
		for code, modname in suitelist[1:]:
327 328
			fp.write(",\n\t\t%s_Events"%modname)
		fp.write(",\n\t\taetools.TalkTo):\n")
329
		fp.write("\t_signature = %s\n\n"%`creatorsignature`)
330
		fp.write("\t_moduleName = '%s'\n\n"%packagename)
331
	fp.close()
332 333 334 335 336
	
def precompilesuite(suite, basepackage=None):
	"""Parse a single suite without generating the output. This step is needed
	so we can resolve recursive references by suites to enums/comps/etc declared
	in other suites"""
337 338 339
	[name, desc, code, level, version, events, classes, comps, enums] = suite
	
	modname = identify(name)
340 341
	if len(modname) > 28:
		modname = modname[:27]
342 343
	fss, ok = macfs.StandardPutFile('Python output file', modname+'.py')
	if not ok:
344 345
		return None, None, None, None, None

346 347
	pathname = fss.as_pathname()
	modname = os.path.splitext(os.path.split(pathname)[1])[0]
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
	
	if basepackage and basepackage._code_to_module.has_key(code):
		# We are an extension of a baseclass (usually an application extending
		# Standard_Suite or so). Import everything from our base module
		basemodule = basepackage._code_to_module[code]
	else:
		# We are not an extension.
		basemodule = None

	enumsneeded = {}
	for event in events:
		findenumsinevent(event, enumsneeded)

	objc = ObjectCompiler(None, basemodule)
	for cls in classes:
		objc.compileclass(cls)
	for cls in classes:
		objc.fillclasspropsandelems(cls)
	for comp in comps:
		objc.compilecomparison(comp)
	for enum in enums:
		objc.compileenumeration(enum)
	
	for enum in enumsneeded.keys():
		objc.checkforenum(enum)
		
	objc.dumpindex()
	
	precompinfo = objc.getprecompinfo(modname)
	
	return code, suite, fss, modname, precompinfo

def compilesuite((suite, fss, modname), major, minor, language, script, fname, basepackage, precompinfo):
	"""Generate code for a single suite"""
	[name, desc, code, level, version, events, classes, comps, enums] = suite
	
	pathname = fss.as_pathname()
385
	fp = open(fss.as_pathname(), 'w')
386
	fss.SetCreatorType('Pyth', 'TEXT')
387
	
388
	fp.write('"""Suite %s: %s\n' % (ascii(name), ascii(desc)))
389
	fp.write("Level %d, version %d\n\n" % (level, version))
390
	fp.write("Generated from %s\n"%ascii(fname))
391 392 393 394 395 396
	fp.write("AETE/AEUT resource version %d/%d, language %d, script %d\n" % \
		(major, minor, language, script))
	fp.write('"""\n\n')
	
	fp.write('import aetools\n')
	fp.write('import MacOS\n\n')
397
	fp.write("_code = %s\n\n"% `code`)
398 399 400
	if basepackage and basepackage._code_to_module.has_key(code):
		# We are an extension of a baseclass (usually an application extending
		# Standard_Suite or so). Import everything from our base module
401
		fp.write('from %s import *\n'%basepackage._code_to_fullname[code][0])
402
		basemodule = basepackage._code_to_module[code]
403 404 405 406
	elif basepackage and basepackage._code_to_module.has_key(code.lower()):
		# This is needed by CodeWarrior and some others.
		fp.write('from %s import *\n'%basepackage._code_to_fullname[code.lower()][0])
		basemodule = basepackage._code_to_module[code.lower()]
407 408 409 410
	else:
		# We are not an extension.
		basemodule = None
	compileclassheader(fp, modname, basemodule)
411 412

	enumsneeded = {}
413 414
	if events:
		for event in events:
415
			compileevent(fp, event, enumsneeded)
416 417
	else:
		fp.write("\tpass\n\n")
418

419
	objc = ObjectCompiler(fp, basemodule, precompinfo)
420 421
	for cls in classes:
		objc.compileclass(cls)
422
	for cls in classes:
423
		objc.fillclasspropsandelems(cls)
424
	for comp in comps:
425 426 427 428 429 430 431 432
		objc.compilecomparison(comp)
	for enum in enums:
		objc.compileenumeration(enum)
	
	for enum in enumsneeded.keys():
		objc.checkforenum(enum)
		
	objc.dumpindex()
433 434
	
	return code, modname
435

436
def compileclassheader(fp, name, module=None):
437
	"""Generate class boilerplate"""
438
	classname = '%s_Events'%name
439 440 441 442
	if module:
		modshortname = string.split(module.__name__, '.')[-1]
		baseclassname = '%s_Events'%modshortname
		fp.write("class %s(%s):\n\n"%(classname, baseclassname))
443 444
	else:
		fp.write("class %s:\n\n"%classname)
445
	
446
def compileevent(fp, event, enumsneeded):
447 448 449 450 451 452 453 454 455
	"""Generate code for a single event"""
	[name, desc, code, subcode, returns, accepts, arguments] = event
	funcname = identify(name)
	#
	# generate name->keyword map
	#
	if arguments:
		fp.write("\t_argmap_%s = {\n"%funcname)
		for a in arguments:
456
			fp.write("\t\t%s : %s,\n"%(`identify(a[0])`, `a[1]`))
457 458 459 460 461 462 463 464
		fp.write("\t}\n\n")
		
	#
	# Generate function header
	#
	has_arg = (not is_null(accepts))
	opt_arg = (has_arg and is_optional(accepts))
	
465
	fp.write("\tdef %s(self, "%funcname)
466
	if has_arg:
467 468 469 470
		if not opt_arg:
			fp.write("_object, ")		# Include direct object, if it has one
		else:
			fp.write("_object=None, ")	# Also include if it is optional
471
	else:
472 473
		fp.write("_no_object=None, ")	# For argument checking
	fp.write("_attributes={}, **_arguments):\n")	# include attribute dict and args
474 475 476 477
	#
	# Generate doc string (important, since it may be the only
	# available documentation, due to our name-remaping)
	#
478
	fp.write('\t\t"""%s: %s\n'%(ascii(name), ascii(desc)))
479 480 481 482 483 484 485 486 487 488 489 490 491 492
	if has_arg:
		fp.write("\t\tRequired argument: %s\n"%getdatadoc(accepts))
	elif opt_arg:
		fp.write("\t\tOptional argument: %s\n"%getdatadoc(accepts))
	for arg in arguments:
		fp.write("\t\tKeyword argument %s: %s\n"%(identify(arg[0]),
				getdatadoc(arg[2])))
	fp.write("\t\tKeyword argument _attributes: AppleEvent attribute dictionary\n")
	if not is_null(returns):
		fp.write("\t\tReturns: %s\n"%getdatadoc(returns))
	fp.write('\t\t"""\n')
	#
	# Fiddle the args so everything ends up in 'arguments' dictionary
	#
493 494
	fp.write("\t\t_code = %s\n"% `code`)
	fp.write("\t\t_subcode = %s\n\n"% `subcode`)
495 496 497 498 499 500 501 502 503 504
	#
	# Do keyword name substitution
	#
	if arguments:
		fp.write("\t\taetools.keysubst(_arguments, self._argmap_%s)\n"%funcname)
	else:
		fp.write("\t\tif _arguments: raise TypeError, 'No optional args expected'\n")
	#
	# Stuff required arg (if there is one) into arguments
	#
505
	if has_arg:
506
		fp.write("\t\t_arguments['----'] = _object\n")
507
	elif opt_arg:
508 509 510 511
		fp.write("\t\tif _object:\n")
		fp.write("\t\t\t_arguments['----'] = _object\n")
	else:
		fp.write("\t\tif _no_object != None: raise TypeError, 'No direct arg expected'\n")
512 513
	fp.write("\n")
	#
514
	# Do enum-name substitution
515 516 517 518 519
	#
	for a in arguments:
		if is_enum(a[2]):
			kname = a[1]
			ename = a[2][0]
520 521
			if ename <> '****':
				fp.write("\t\taetools.enumsubst(_arguments, %s, _Enum_%s)\n" %
522
					(`kname`, identify(ename)))
523
				enumsneeded[ename] = 1
524 525 526 527
	fp.write("\n")
	#
	# Do the transaction
	#
528 529
	fp.write("\t\t_reply, _arguments, _attributes = self.send(_code, _subcode,\n")
	fp.write("\t\t\t\t_arguments, _attributes)\n")
530 531 532
	#
	# Error handling
	#
533
	fp.write("\t\tif _arguments.get('errn', 0):\n")
534
	fp.write("\t\t\traise aetools.Error, aetools.decodeerror(_arguments)\n")
535 536 537 538
	fp.write("\t\t# XXXX Optionally decode result\n")
	#
	# Decode result
	#
539
	fp.write("\t\tif _arguments.has_key('----'):\n")
540 541
	if is_enum(returns):
		fp.write("\t\t\t# XXXX Should do enum remapping here...\n")
542
	fp.write("\t\t\treturn _arguments['----']\n")
543 544 545 546 547 548 549 550 551 552 553 554
	fp.write("\n")
	
#	print "\n#    Command %s -- %s (%s, %s)" % (`name`, `desc`, `code`, `subcode`)
#	print "#        returns", compiledata(returns)
#	print "#        accepts", compiledata(accepts)
#	for arg in arguments:
#		compileargument(arg)

def compileargument(arg):
	[name, keyword, what] = arg
	print "#        %s (%s)" % (name, `keyword`), compiledata(what)

555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
def findenumsinevent(event, enumsneeded):
	"""Find all enums for a single event"""
	[name, desc, code, subcode, returns, accepts, arguments] = event
	for a in arguments:
		if is_enum(a[2]):
			ename = a[2][0]
			if ename <> '****':
				enumsneeded[ename] = 1

#
# This class stores the code<->name translations for a single module. It is used
# to keep the information while we're compiling the module, but we also keep these objects
# around so if one suite refers to, say, an enum in another suite we know where to
# find it. Finally, if we really can't find a code, the user can add modules by
# hand.
#
class CodeNameMapper:
	
	def __init__(self):
		self.code2name = {
			"property" : {},
			"class" : {},
			"enum" : {},
			"comparison" : {},
		}
		self.name2code =  {
			"property" : {},
			"class" : {},
			"enum" : {},
			"comparison" : {},
		}
		self.modulename = None
		self.star_imported = 0
		
	def addnamecode(self, type, name, code):
		self.name2code[type][name] = code
		if not self.code2name[type].has_key(code):
			self.code2name[type][code] = name
		
	def hasname(self, type, name):
		return self.name2code[type].has_key(name)
		
	def hascode(self, type, code):
		return self.code2name[type].has_key(code)
		
	def findcodename(self, type, code):
		if not self.hascode(type, code):
			return None, None, None
		name = self.code2name[type][code]
		if self.modulename and not self.star_imported:
			qualname = '%s.%s'%(self.modulename, name)
		else:
			qualname = name
		return name, qualname, self.modulename
		
	def getall(self, type):
		return self.code2name[type].items()
			
	def addmodule(self, module, name, star_imported):
		self.modulename = name
		self.star_imported = star_imported
		for code, name in module._propdeclarations.items():
			self.addnamecode('property', name, code)
		for code, name in module._classdeclarations.items():
			self.addnamecode('class', name, code)
		for code in module._enumdeclarations.keys():
			self.addnamecode('enum', '_Enum_'+identify(code), code)
		for code, name in module._compdeclarations.items():
			self.addnamecode('comparison', name, code)
		
	def prepareforexport(self, name=None):
		if not self.modulename:
			self.modulename = name
		return self
			
630
class ObjectCompiler:
631
	def __init__(self, fp, basesuite=None, othernamemappers=None):
632
		self.fp = fp
633
		self.basesuite = basesuite
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
		self.namemappers = [CodeNameMapper()]
		if othernamemappers:
			self.othernamemappers = othernamemappers[:]
		else:
			self.othernamemappers = []
		if basesuite:
			basemapper = CodeNameMapper()
			basemapper.addmodule(basesuite, '', 1)
			self.namemappers.append(basemapper)
		
	def getprecompinfo(self, modname):
		list = []
		for mapper in self.namemappers:
			emapper = mapper.prepareforexport(modname)
			if emapper:
				list.append(emapper)
		return list
651 652 653
		
	def findcodename(self, type, code):
		while 1:
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
			# First try: check whether we already know about this code.
			for mapper in self.namemappers:
				if mapper.hascode(type, code):
					return mapper.findcodename(type, code)
			# Second try: maybe one of the other modules knows about it.
			for mapper in self.othernamemappers:
				if mapper.hascode(type, code):
					self.othernamemappers.remove(mapper)
					self.namemappers.append(mapper)
					if self.fp:
						self.fp.write("import %s\n"%mapper.modulename)
					break
			else:
				# If all this has failed we ask the user for a guess on where it could
				# be and retry.
				if self.fp:
					m = self.askdefinitionmodule(type, code)
				else:
					m = None
				if not m: return None, None, None
				mapper = CodeNameMapper()
				mapper.addmodule(m, m.__name__, 0)
				self.namemappers.append(mapper)
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
	
	def askdefinitionmodule(self, type, code):
		fss, ok = macfs.PromptGetFile('Where is %s %s declared?'%(type, code))
		if not ok: return
		path, file = os.path.split(fss.as_pathname())
		modname = os.path.splitext(file)[0]
		if not path in sys.path:
			sys.path.insert(0, path)
		m = __import__(modname)
		self.fp.write("import %s\n"%modname)
		return m
		
	def compileclass(self, cls):
		[name, code, desc, properties, elements] = cls
		pname = identify(name)
692
		if self.namemappers[0].hascode('class', code):
693
			# plural forms and such
694 695 696
			othername, dummy, dummy = self.namemappers[0].findcodename('class', code)
			if self.fp:
				self.fp.write("\n%s = %s\n"%(pname, othername))
697
		else:
698 699
			if self.fp:
				self.fp.write('\nclass %s(aetools.ComponentItem):\n' % pname)
700
				self.fp.write('\t"""%s - %s """\n' % (ascii(name), ascii(desc)))
701 702
				self.fp.write('\twant = %s\n' % `code`)
		self.namemappers[0].addnamecode('class', pname, code)
703 704 705 706 707 708 709 710 711 712 713
		for prop in properties:
			self.compileproperty(prop)
		for elem in elements:
			self.compileelement(elem)
	
	def compileproperty(self, prop):
		[name, code, what] = prop
		if code == 'c@#!':
			# Something silly with plurals. Skip it.
			return
		pname = identify(name)
714
		if self.namemappers[0].hascode('property', code):
715 716
			# plural forms and such
			othername, dummy, dummy = self.namemappers[0].findcodename('property', code)
717 718
			if pname == othername:
				return
719
			if self.fp:
720
				self.fp.write("\n%s = %s\n"%(pname, othername))
721
		else:
722 723
			if self.fp:
				self.fp.write("class %s(aetools.NProperty):\n" % pname)
724
				self.fp.write('\t"""%s - %s """\n' % (ascii(name), ascii(what[1])))
725 726
				self.fp.write("\twhich = %s\n" % `code`)
				self.fp.write("\twant = %s\n" % `what[0]`)
727
		self.namemappers[0].addnamecode('property', pname, code)
728 729 730
	
	def compileelement(self, elem):
		[code, keyform] = elem
731
		if self.fp:
732
			self.fp.write("#        element %s as %s\n" % (`code`, keyform))
733 734 735 736

	def fillclasspropsandelems(self, cls):
		[name, code, desc, properties, elements] = cls
		cname = identify(name)
737 738
		if self.namemappers[0].hascode('class', code) and \
				self.namemappers[0].findcodename('class', code)[0] != cname:
739 740 741 742
			# This is an other name (plural or so) for something else. Skip.
			return
		plist = []
		elist = []
743
		superclasses = []
744 745
		for prop in properties:
			[pname, pcode, what] = prop
746 747
			if pcode == "c@#^":
				superclasses.append(what)
748 749 750 751
			if pcode == 'c@#!':
				continue
			pname = identify(pname)
			plist.append(pname)
752 753 754 755 756 757 758 759 760 761

		superclassnames = []
		for superclass in superclasses:
			superId, superDesc, dummy = superclass
			superclassname, fullyqualifiedname, module = self.findcodename("class", superId)
			superclassnames.append(superclassname)

		if self.fp:
			self.fp.write("%s._superclassnames = %s\n"%(cname, `superclassnames`))

762 763 764 765 766 767
		for elem in elements:
			[ecode, keyform] = elem
			if ecode == 'c@#!':
				continue
			name, ename, module = self.findcodename('class', ecode)
			if not name:
768
				if self.fp:
769
					self.fp.write("# XXXX %s element %s not found!!\n"%(cname, `ecode`))
770
			else:
Jack Jansen's avatar
Jack Jansen committed
771
				elist.append((name, ename))
772 773
		
		if self.fp:
774
			self.fp.write("%s._privpropdict = {\n"%cname)
775 776 777
			for n in plist:
				self.fp.write("\t'%s' : %s,\n"%(n, n))
			self.fp.write("}\n")
778
			self.fp.write("%s._privelemdict = {\n"%cname)
779 780 781
			for n, fulln in elist:
				self.fp.write("\t'%s' : %s,\n"%(n, fulln))
			self.fp.write("}\n")
782 783 784 785
	
	def compilecomparison(self, comp):
		[name, code, comment] = comp
		iname = identify(name)
786 787 788
		self.namemappers[0].addnamecode('comparison', iname, code)
		if self.fp:
			self.fp.write("class %s(aetools.NComparison):\n" % iname)
789
			self.fp.write('\t"""%s - %s """\n' % (ascii(name), ascii(comment)))
790 791 792 793
		
	def compileenumeration(self, enum):
		[code, items] = enum
		name = "_Enum_%s" % identify(code)
794 795 796 797 798 799
		if self.fp:
			self.fp.write("%s = {\n" % name)
			for item in items:
				self.compileenumerator(item)
			self.fp.write("}\n\n")
		self.namemappers[0].addnamecode('enum', name, code)
800 801 802 803
		return code
	
	def compileenumerator(self, item):
		[name, code, desc] = item
804
		self.fp.write("\t%s : %s,\t# %s\n" % (`identify(name)`, `code`, ascii(desc)))
805 806 807 808 809
		
	def checkforenum(self, enum):
		"""This enum code is used by an event. Make sure it's available"""
		name, fullname, module = self.findcodename('enum', enum)
		if not name:
810
			if self.fp:
811
				self.fp.write("_Enum_%s = None # XXXX enum %s not found!!\n"%(identify(enum), ascii(enum)))
812 813
			return
		if module:
814 815
			if self.fp:
				self.fp.write("from %s import %s\n"%(module, name))
816 817
		
	def dumpindex(self):
818 819
		if not self.fp:
			return
820 821
		self.fp.write("\n#\n# Indices of types declared in this module\n#\n")
		self.fp.write("_classdeclarations = {\n")
822
		for k, v in self.namemappers[0].getall('class'):
823
			self.fp.write("\t%s : %s,\n" % (`k`, v))
824 825
		self.fp.write("}\n")
		self.fp.write("\n_propdeclarations = {\n")
826
		for k, v in self.namemappers[0].getall('property'):
827
			self.fp.write("\t%s : %s,\n" % (`k`, v))
828 829
		self.fp.write("}\n")
		self.fp.write("\n_compdeclarations = {\n")
830
		for k, v in self.namemappers[0].getall('comparison'):
831
			self.fp.write("\t%s : %s,\n" % (`k`, v))
832 833
		self.fp.write("}\n")
		self.fp.write("\n_enumdeclarations = {\n")
834
		for k, v in self.namemappers[0].getall('enum'):
835
			self.fp.write("\t%s : %s,\n" % (`k`, v))
836
		self.fp.write("}\n")
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853

def compiledata(data):
	[type, description, flags] = data
	return "%s -- %s %s" % (`type`, `description`, compiledataflags(flags))
	
def is_null(data):
	return data[0] == 'null'
	
def is_optional(data):
	return (data[2] & 0x8000)
	
def is_enum(data):
	return (data[2] & 0x2000)
	
def getdatadoc(data):
	[type, descr, flags] = data
	if descr:
854
		return ascii(descr)
855 856 857 858
	if type == '****':
		return 'anything'
	if type == 'obj ':
		return 'an AE object reference'
859
	return "undocumented, typecode %s"%`type`
860 861 862 863 864 865 866 867 868 869 870 871

dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
def compiledataflags(flags):
	bits = []
	for i in range(16):
		if flags & (1<<i):
			if i in dataflagdict.keys():
				bits.append(dataflagdict[i])
			else:
				bits.append(`i`)
	return '[%s]' % string.join(bits)
	
872 873 874 875 876 877 878 879 880
def ascii(str):
	"""Return a string with all non-ascii characters hex-encoded"""
	if type(str) != type(''):
		return map(ascii, str)
	rv = ''
	for c in str:
		if c in ('\t', '\n', '\r') or ' ' <= c < chr(0x7f):
			rv = rv + c
		else:
881
			rv = rv + '\\' + 'x%02.2x' % ord(c)
882 883
	return rv
	
884 885 886 887 888 889
def identify(str):
	"""Turn any string into an identifier:
	- replace space by _
	- replace other illegal chars by _xx_ (hex code)
	- prepend _ if the result is a python keyword
	"""
890
	if not str:
891
		return "empty_ae_name_"
892
	rv = ''
893
	ok = string.ascii_letters + '_'
894 895 896 897 898 899 900 901 902
	ok2 = ok + string.digits
	for c in str:
		if c in ok:
			rv = rv + c
		elif c == ' ':
			rv = rv + '_'
		else:
			rv = rv + '_%02.2x_'%ord(c)
		ok = ok2
903
	if keyword.iskeyword(rv):
904
		rv = rv + '_'
905 906 907 908 909 910 911
	return rv

# Call the main program

if __name__ == '__main__':
	main()
	sys.exit(1)
912
print identify('for')