cpython/Lib/plat-mac/gensuitemodule.py

1206 lines
44 KiB
Python
Raw Normal View History

"""
gensuitemodule - Generate an AE suite module from an aete/aeut resource
Based on aete.py.
Reading and understanding this code is left as an exercise to the reader.
"""
import MacOS
2003-01-26 16:35:47 -04:00
import EasyDialogs
import os
import string
import sys
import types
import StringIO
import keyword
import macresource
import aetools
import distutils.sysconfig
import OSATerminology
from Carbon.Res import *
import Carbon.Folder
import MacOS
import getopt
import plistlib
_MAC_LIB_FOLDER=os.path.dirname(aetools.__file__)
DEFAULT_STANDARD_PACKAGEFOLDER=os.path.join(_MAC_LIB_FOLDER, 'lib-scriptpackages')
DEFAULT_USER_PACKAGEFOLDER=distutils.sysconfig.get_python_lib()
def usage():
2003-04-09 10:25:43 -03:00
sys.stderr.write("Usage: %s [opts] application-or-resource-file\n" % sys.argv[0])
sys.stderr.write("""Options:
--output pkgdir Pathname of the output package (short: -o)
--resource Parse resource file in stead of launching application (-r)
--base package Use another base package in stead of default StdSuites (-b)
--edit old=new Edit suite names, use empty new to skip a suite (-e)
--creator code Set creator code for package (-c)
--dump Dump aete resource to stdout in stead of creating module (-d)
--verbose Tell us what happens (-v)
""")
2003-04-09 10:25:43 -03:00
sys.exit(1)
def main():
2003-04-09 10:25:43 -03:00
if len(sys.argv) > 1:
SHORTOPTS = "rb:o:e:c:dv"
LONGOPTS = ("resource", "base=", "output=", "edit=", "creator=", "dump", "verbose")
try:
opts, args = getopt.getopt(sys.argv[1:], SHORTOPTS, LONGOPTS)
except getopt.GetoptError:
usage()
process_func = processfile
basepkgname = 'StdSuites'
output = None
edit_modnames = []
creatorsignature = None
dump = None
verbose = None
for o, a in opts:
if o in ('-r', '--resource'):
process_func = processfile_fromresource
if o in ('-b', '--base'):
basepkgname = a
if o in ('-o', '--output'):
output = a
if o in ('-e', '--edit'):
split = a.split('=')
if len(split) != 2:
usage()
edit_modnames.append(split)
if o in ('-c', '--creator'):
if len(a) != 4:
sys.stderr.write("creator must be 4-char string\n")
sys.exit(1)
creatorsignature = a
if o in ('-d', '--dump'):
dump = sys.stdout
if o in ('-v', '--verbose'):
verbose = sys.stderr
if output and len(args) > 1:
sys.stderr.write("%s: cannot specify --output with multiple inputs\n" % sys.argv[0])
sys.exit(1)
for filename in args:
process_func(filename, output=output, basepkgname=basepkgname,
edit_modnames=edit_modnames, creatorsignature=creatorsignature,
dump=dump, verbose=verbose)
else:
main_interactive()
def main_interactive(interact=0, basepkgname='StdSuites'):
2003-04-09 10:25:43 -03:00
if interact:
# Ask for save-filename for each module
edit_modnames = None
else:
# Use default filenames for each module
edit_modnames = []
appsfolder = Carbon.Folder.FSFindFolder(-32765, 'apps', 0)
filename = EasyDialogs.AskFileForOpen(
message='Select scriptable application',
dialogOptionFlags=0x1056, # allow selection of .app bundles
defaultLocation=appsfolder)
if not filename:
return
if not is_scriptable(filename):
if EasyDialogs.AskYesNoCancel(
"Warning: application does not seem scriptable",
yes="Continue", default=2, no="") <= 0:
return
try:
processfile(filename, edit_modnames=edit_modnames, basepkgname=basepkgname,
verbose=sys.stderr)
except MacOS.Error, arg:
print "Error getting terminology:", arg
print "Retry, manually parsing resources"
processfile_fromresource(filename, edit_modnames=edit_modnames,
basepkgname=basepkgname, verbose=sys.stderr)
def is_scriptable(application):
2003-04-09 10:25:43 -03:00
"""Return true if the application is scriptable"""
if os.path.isdir(application):
plistfile = os.path.join(application, 'Contents', 'Info.plist')
if not os.path.exists(plistfile):
return False
plist = plistlib.Plist.fromFile(plistfile)
return plist.get('NSAppleScriptEnabled', False)
# If it is a file test for an aete/aeut resource.
currf = CurResFile()
try:
refno = macresource.open_pathname(application)
except MacOS.Error:
return False
UseResFile(refno)
n_terminology = Count1Resources('aete') + Count1Resources('aeut') + \
Count1Resources('scsz') + Count1Resources('osiz')
CloseResFile(refno)
UseResFile(currf)
return n_terminology > 0
def processfile_fromresource(fullname, output=None, basepkgname=None,
2003-04-09 10:25:43 -03:00
edit_modnames=None, creatorsignature=None, dump=None, verbose=None):
"""Process all resources in a single file"""
if not is_scriptable(fullname) and verbose:
print >>verbose, "Warning: app does not seem scriptable: %s" % fullname
cur = CurResFile()
if verbose:
print >>verbose, "Processing", fullname
rf = macresource.open_pathname(fullname)
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)
if verbose:
print >>verbose, "\nLISTING aete+aeut RESOURCES IN", `fullname`
aetelist = []
for res in resources:
if verbose:
print >>verbose, "decoding", res.GetResInfo(), "..."
data = res.data
aete = decode(data, verbose)
aetelist.append((aete, res.GetResInfo()))
finally:
if rf <> cur:
CloseResFile(rf)
UseResFile(cur)
# switch back (needed for dialogs in Python)
UseResFile(cur)
if dump:
dumpaetelist(aetelist, dump)
compileaetelist(aetelist, fullname, output=output,
basepkgname=basepkgname, edit_modnames=edit_modnames,
creatorsignature=creatorsignature, verbose=verbose)
def processfile(fullname, output=None, basepkgname=None,
2003-04-09 10:25:43 -03:00
edit_modnames=None, creatorsignature=None, dump=None,
verbose=None):
"""Ask an application for its terminology and process that"""
if not is_scriptable(fullname) and verbose:
print >>verbose, "Warning: app does not seem scriptable: %s" % fullname
if verbose:
print >>verbose, "\nASKING FOR aete DICTIONARY IN", `fullname`
try:
aedescobj, launched = OSATerminology.GetAppTerminology(fullname)
except MacOS.Error, arg:
if arg[0] in (-1701, -192): # errAEDescNotFound, resNotFound
if verbose:
print >>verbose, "GetAppTerminology failed with errAEDescNotFound/resNotFound, trying manually"
aedata, sig = getappterminology(fullname, verbose=verbose)
if not creatorsignature:
creatorsignature = sig
else:
raise
else:
if launched:
if verbose:
print >>verbose, "Launched", fullname
raw = aetools.unpack(aedescobj)
if not raw:
if verbose:
print >>verbose, 'Unpack returned empty value:', raw
return
if not raw[0].data:
if verbose:
print >>verbose, 'Unpack returned value without data:', raw
return
aedata = raw[0]
aete = decode(aedata.data, verbose)
if dump:
dumpaetelist([aete], dump)
return
compileaete(aete, None, fullname, output=output, basepkgname=basepkgname,
creatorsignature=creatorsignature, edit_modnames=edit_modnames,
verbose=verbose)
def getappterminology(fullname, verbose=None):
2003-04-09 10:25:43 -03:00
"""Get application terminology by sending an AppleEvent"""
# First check that we actually can send AppleEvents
if not MacOS.WMAvailable():
raise RuntimeError, "Cannot send AppleEvents, no access to window manager"
# Next, a workaround for a bug in MacOS 10.2: sending events will hang unless
# you have created an event loop first.
import Carbon.Evt
Carbon.Evt.WaitNextEvent(0,0)
if os.path.isdir(fullname):
# Now get the signature of the application, hoping it is a bundle
pkginfo = os.path.join(fullname, 'Contents', 'PkgInfo')
if not os.path.exists(pkginfo):
raise RuntimeError, "No PkgInfo file found"
tp_cr = open(pkginfo, 'rb').read()
cr = tp_cr[4:8]
else:
# Assume it is a file
cr, tp = MacOS.GetCreatorAndType(fullname)
# Let's talk to it and ask for its AETE
talker = aetools.TalkTo(cr)
try:
talker._start()
except (MacOS.Error, aetools.Error), arg:
if verbose:
print >>verbose, 'Warning: start() failed, continuing anyway:', arg
reply = talker.send("ascr", "gdte")
#reply2 = talker.send("ascr", "gdut")
# Now pick the bits out of the return that we need.
return reply[1]['----'], cr
def compileaetelist(aetelist, fullname, output=None, basepkgname=None,
2003-04-09 10:25:43 -03:00
edit_modnames=None, creatorsignature=None, verbose=None):
for aete, resinfo in aetelist:
compileaete(aete, resinfo, fullname, output=output,
basepkgname=basepkgname, edit_modnames=edit_modnames,
creatorsignature=creatorsignature, verbose=verbose)
def dumpaetelist(aetelist, output):
2003-04-09 10:25:43 -03:00
import pprint
pprint.pprint(aetelist, output)
def decode(data, verbose=None):
2003-04-09 10:25:43 -03:00
"""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 and verbose:
verbose.write("%d processed + %d unprocessed = %d total\n" %
(processed, unprocessed, total))
return aete
def simplify(item):
2003-04-09 10:25:43 -03:00
"""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):
2003-04-09 10:25:43 -03:00
c = f.read(1)
if not c:
raise EOFError, 'in getbyte' + str(args)
return ord(c)
def getword(f, *args):
2003-04-09 10:25:43 -03:00
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):
2003-04-09 10:25:43 -03:00
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):
2003-04-09 10:25:43 -03:00
getalign(f)
s = f.read(4)
if len(s) < 4:
raise EOFError, 'in getostype' + str(args)
return s
def getpstr(f, *args):
2003-04-09 10:25:43 -03:00
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):
2003-04-09 10:25:43 -03:00
if f.tell() & 1:
c = f.read(1)
##if c <> '\0':
## print 'align:', `c`
def getlist(f, description, getitem):
2003-04-09 10:25:43 -03:00
count = getword(f)
list = []
for i in range(count):
list.append(generic(getitem, f))
getalign(f)
return list
def alt_generic(what, f, *args):
2003-04-09 10:25:43 -03:00
print "generic", `what`, args
res = vageneric(what, f, args)
print '->', `res`
return res
def generic(what, f, *args):
2003-04-09 10:25:43 -03:00
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 = [
2003-04-09 10:25:43 -03:00
(getostype, "type"),
(getpstr, "description"),
(getword, "flags")
]
getargument = [
2003-04-09 10:25:43 -03:00
(getpstr, "name"),
(getostype, "keyword"),
(getdata, "what")
]
getevent = [
2003-04-09 10:25:43 -03:00
(getpstr, "name"),
(getpstr, "description"),
(getostype, "suite code"),
(getostype, "event code"),
(getdata, "returns"),
(getdata, "accepts"),
(getlist, "optional arguments", getargument)
]
getproperty = [
2003-04-09 10:25:43 -03:00
(getpstr, "name"),
(getostype, "code"),
(getdata, "what")
]
getelement = [
2003-04-09 10:25:43 -03:00
(getostype, "type"),
(getlist, "keyform", getostype)
]
getclass = [
2003-04-09 10:25:43 -03:00
(getpstr, "name"),
(getostype, "class code"),
(getpstr, "description"),
(getlist, "properties", getproperty),
(getlist, "elements", getelement)
]
getcomparison = [
2003-04-09 10:25:43 -03:00
(getpstr, "operator name"),
(getostype, "operator ID"),
(getpstr, "operator comment"),
]
getenumerator = [
2003-04-09 10:25:43 -03:00
(getpstr, "enumerator name"),
(getostype, "enumerator ID"),
(getpstr, "enumerator comment")
]
getenumeration = [
2003-04-09 10:25:43 -03:00
(getostype, "enumeration ID"),
(getlist, "enumerator", getenumerator)
]
getsuite = [
2003-04-09 10:25:43 -03:00
(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 = [
2003-04-09 10:25:43 -03:00
(getword, "major/minor version in BCD"),
(getword, "language code"),
(getword, "script code"),
(getlist, "suites", getsuite)
]
def compileaete(aete, resinfo, fname, output=None, basepkgname=None,
2003-04-09 10:25:43 -03:00
edit_modnames=None, creatorsignature=None, verbose=None):
"""Generate code for a full aete resource. fname passed for doc purposes"""
[version, language, script, suites] = aete
major, minor = divmod(version, 256)
if not creatorsignature:
creatorsignature, dummy = MacOS.GetCreatorAndType(fname)
packagename = identify(os.path.splitext(os.path.basename(fname))[0])
if language:
packagename = packagename+'_lang%d'%language
if script:
packagename = packagename+'_script%d'%script
if len(packagename) > 27:
packagename = packagename[:27]
if output:
# XXXX Put this in site-packages if it isn't a full pathname?
if not os.path.exists(output):
os.mkdir(output)
pathname = output
else:
pathname = EasyDialogs.AskFolder(message='Create and select package folder for %s'%packagename,
defaultLocation=DEFAULT_USER_PACKAGEFOLDER)
output = pathname
if not pathname:
return
packagename = os.path.split(os.path.normpath(pathname))[1]
if not basepkgname:
basepkgname = EasyDialogs.AskFolder(message='Package folder for base suite (usually StdSuites)',
defaultLocation=DEFAULT_STANDARD_PACKAGEFOLDER)
if basepkgname:
dirname, basepkgname = os.path.split(os.path.normpath(basepkgname))
if dirname and not dirname in sys.path:
sys.path.insert(0, dirname)
basepackage = __import__(basepkgname)
else:
basepackage = None
suitelist = []
allprecompinfo = []
allsuites = []
for suite in suites:
compiler = SuiteCompiler(suite, basepackage, output, edit_modnames, verbose)
code, modname, precompinfo = compiler.precompilesuite()
if not code:
continue
allprecompinfo = allprecompinfo + precompinfo
suiteinfo = suite, pathname, modname
suitelist.append((code, modname))
allsuites.append(compiler)
for compiler in allsuites:
compiler.compilesuite(major, minor, language, script, fname, allprecompinfo)
initfilename = os.path.join(output, '__init__.py')
fp = open(initfilename, 'w')
MacOS.SetCreatorAndType(initfilename, 'Pyth', 'TEXT')
fp.write('"""\n')
fp.write("Package generated from %s\n"%ascii(fname))
if resinfo:
fp.write("Resource %s resid %d %s\n"%(ascii(resinfo[1]), resinfo[0], ascii(resinfo[2])))
fp.write('"""\n')
fp.write('import aetools\n')
fp.write('Error = aetools.Error\n')
suitelist.sort()
for code, modname in suitelist:
fp.write("import %s\n" % modname)
fp.write("\n\n_code_to_module = {\n")
for code, modname in suitelist:
fp.write(" '%s' : %s,\n"%(ascii(code), modname))
fp.write("}\n\n")
fp.write("\n\n_code_to_fullname = {\n")
for code, modname in suitelist:
fp.write(" '%s' : ('%s.%s', '%s'),\n"%(ascii(code), packagename, modname, modname))
fp.write("}\n\n")
for code, modname in suitelist:
fp.write("from %s import *\n"%modname)
# Generate property dicts and element dicts for all types declared in this module
fp.write("\ndef getbaseclasses(v):\n")
fp.write(" if not getattr(v, '_propdict', None):\n")
fp.write(" v._propdict = {}\n")
fp.write(" v._elemdict = {}\n")
fp.write(" for superclassname in getattr(v, '_superclassnames', []):\n")
fp.write(" superclass = eval(superclassname)\n")
fp.write(" getbaseclasses(superclass)\n")
fp.write(" v._propdict.update(getattr(superclass, '_propdict', {}))\n")
fp.write(" v._elemdict.update(getattr(superclass, '_elemdict', {}))\n")
fp.write(" v._propdict.update(getattr(v, '_privpropdict', {}))\n")
fp.write(" v._elemdict.update(getattr(v, '_privelemdict', {}))\n")
fp.write("\n")
fp.write("import StdSuites\n")
allprecompinfo.sort()
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)
2003-04-09 10:25:43 -03:00
# 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'):
fp.write(" %s : %s,\n" % (`k`, v))
fp.write("}\n")
2003-04-09 10:25:43 -03:00
if suitelist:
fp.write("\n\nclass %s(%s_Events"%(packagename, suitelist[0][1]))
for code, modname in suitelist[1:]:
fp.write(",\n %s_Events"%modname)
fp.write(",\n aetools.TalkTo):\n")
fp.write(" _signature = %s\n\n"%`creatorsignature`)
fp.write(" _moduleName = '%s'\n\n"%packagename)
fp.close()
class SuiteCompiler:
2003-04-09 10:25:43 -03:00
def __init__(self, suite, basepackage, output, edit_modnames, verbose):
self.suite = suite
self.basepackage = basepackage
self.edit_modnames = edit_modnames
self.output = output
self.verbose = verbose
# Set by precompilesuite
self.pathname = None
self.modname = None
# Set by compilesuite
self.fp = None
self.basemodule = None
self.enumsneeded = {}
def precompilesuite(self):
"""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"""
[name, desc, code, level, version, events, classes, comps, enums] = self.suite
modname = identify(name)
if len(modname) > 28:
modname = modname[:27]
if self.edit_modnames is None:
self.pathname = EasyDialogs.AskFileForSave(message='Python output file',
savedFileName=modname+'.py')
else:
for old, new in self.edit_modnames:
if old == modname:
modname = new
if modname:
self.pathname = os.path.join(self.output, modname + '.py')
else:
self.pathname = None
if not self.pathname:
return None, None, None
self.modname = os.path.splitext(os.path.split(self.pathname)[1])[0]
if self.basepackage and self.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 = self.basepackage._code_to_module[code]
else:
# We are not an extension.
basemodule = None
self.enumsneeded = {}
for event in events:
self.findenumsinevent(event)
objc = ObjectCompiler(None, self.modname, basemodule, interact=(self.edit_modnames is None),
verbose=self.verbose)
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 self.enumsneeded.keys():
objc.checkforenum(enum)
objc.dumpindex()
precompinfo = objc.getprecompinfo(self.modname)
return code, self.modname, precompinfo
def compilesuite(self, major, minor, language, script, fname, precompinfo):
"""Generate code for a single suite"""
[name, desc, code, level, version, events, classes, comps, enums] = self.suite
# Sort various lists, so re-generated source is easier compared
def class_sorter(k1, k2):
"""Sort classes by code, and make sure main class sorts before synonyms"""
# [name, code, desc, properties, elements] = cls
if k1[1] < k2[1]: return -1
if k1[1] > k2[1]: return 1
if not k2[3] or k2[3][0][1] == 'c@#!':
# This is a synonym, the other one is better
return -1
if not k1[3] or k1[3][0][1] == 'c@#!':
# This is a synonym, the other one is better
return 1
return 0
events.sort()
classes.sort(class_sorter)
comps.sort()
enums.sort()
self.fp = fp = open(self.pathname, 'w')
MacOS.SetCreatorAndType(self.pathname, 'Pyth', 'TEXT')
fp.write('"""Suite %s: %s\n' % (ascii(name), ascii(desc)))
fp.write("Level %d, version %d\n\n" % (level, version))
fp.write("Generated from %s\n"%ascii(fname))
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')
fp.write("_code = %s\n\n"% `code`)
if self.basepackage and self.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
fp.write('from %s import *\n'%self.basepackage._code_to_fullname[code][0])
basemodule = self.basepackage._code_to_module[code]
elif self.basepackage and self.basepackage._code_to_module.has_key(code.lower()):
# This is needed by CodeWarrior and some others.
fp.write('from %s import *\n'%self.basepackage._code_to_fullname[code.lower()][0])
basemodule = self.basepackage._code_to_module[code.lower()]
else:
# We are not an extension.
basemodule = None
self.basemodule = basemodule
self.compileclassheader()
self.enumsneeded = {}
if events:
for event in events:
self.compileevent(event)
else:
fp.write(" pass\n\n")
objc = ObjectCompiler(fp, self.modname, basemodule, precompinfo, interact=(self.edit_modnames is None),
verbose=self.verbose)
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 self.enumsneeded.keys():
objc.checkforenum(enum)
objc.dumpindex()
def compileclassheader(self):
"""Generate class boilerplate"""
classname = '%s_Events'%self.modname
if self.basemodule:
modshortname = string.split(self.basemodule.__name__, '.')[-1]
baseclassname = '%s_Events'%modshortname
self.fp.write("class %s(%s):\n\n"%(classname, baseclassname))
else:
self.fp.write("class %s:\n\n"%classname)
def compileevent(self, event):
"""Generate code for a single event"""
[name, desc, code, subcode, returns, accepts, arguments] = event
fp = self.fp
funcname = identify(name)
#
# generate name->keyword map
#
if arguments:
fp.write(" _argmap_%s = {\n"%funcname)
for a in arguments:
fp.write(" %s : %s,\n"%(`identify(a[0])`, `a[1]`))
fp.write(" }\n\n")
#
# Generate function header
#
has_arg = (not is_null(accepts))
opt_arg = (has_arg and is_optional(accepts))
fp.write(" def %s(self, "%funcname)
if has_arg:
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
else:
fp.write("_no_object=None, ") # For argument checking
fp.write("_attributes={}, **_arguments):\n") # include attribute dict and args
#
# Generate doc string (important, since it may be the only
# available documentation, due to our name-remaping)
#
fp.write(' """%s: %s\n'%(ascii(name), ascii(desc)))
if has_arg:
fp.write(" Required argument: %s\n"%getdatadoc(accepts))
elif opt_arg:
fp.write(" Optional argument: %s\n"%getdatadoc(accepts))
for arg in arguments:
fp.write(" Keyword argument %s: %s\n"%(identify(arg[0]),
getdatadoc(arg[2])))
fp.write(" Keyword argument _attributes: AppleEvent attribute dictionary\n")
if not is_null(returns):
fp.write(" Returns: %s\n"%getdatadoc(returns))
fp.write(' """\n')
#
# Fiddle the args so everything ends up in 'arguments' dictionary
#
fp.write(" _code = %s\n"% `code`)
fp.write(" _subcode = %s\n\n"% `subcode`)
#
# Do keyword name substitution
#
if arguments:
fp.write(" aetools.keysubst(_arguments, self._argmap_%s)\n"%funcname)
else:
fp.write(" if _arguments: raise TypeError, 'No optional args expected'\n")
#
# Stuff required arg (if there is one) into arguments
#
if has_arg:
fp.write(" _arguments['----'] = _object\n")
elif opt_arg:
fp.write(" if _object:\n")
fp.write(" _arguments['----'] = _object\n")
else:
fp.write(" if _no_object != None: raise TypeError, 'No direct arg expected'\n")
fp.write("\n")
#
# Do enum-name substitution
#
for a in arguments:
if is_enum(a[2]):
kname = a[1]
ename = a[2][0]
if ename <> '****':
fp.write(" aetools.enumsubst(_arguments, %s, _Enum_%s)\n" %
(`kname`, identify(ename)))
self.enumsneeded[ename] = 1
fp.write("\n")
#
# Do the transaction
#
fp.write(" _reply, _arguments, _attributes = self.send(_code, _subcode,\n")
fp.write(" _arguments, _attributes)\n")
#
# Error handling
#
fp.write(" if _arguments.get('errn', 0):\n")
fp.write(" raise aetools.Error, aetools.decodeerror(_arguments)\n")
fp.write(" # XXXX Optionally decode result\n")
#
# Decode result
#
fp.write(" if _arguments.has_key('----'):\n")
if is_enum(returns):
fp.write(" # XXXX Should do enum remapping here...\n")
fp.write(" return _arguments['----']\n")
fp.write("\n")
def findenumsinevent(self, event):
"""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 <> '****':
self.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:
2003-04-09 10:25:43 -03:00
def __init__(self, interact=1, verbose=None):
self.code2name = {
"property" : {},
"class" : {},
"enum" : {},
"comparison" : {},
}
self.name2code = {
"property" : {},
"class" : {},
"enum" : {},
"comparison" : {},
}
self.modulename = None
self.star_imported = 0
self.can_interact = interact
self.verbose = verbose
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, name):
for dict in self.name2code.values():
if dict.has_key(name):
return True
return False
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
class ObjectCompiler:
2003-04-09 10:25:43 -03:00
def __init__(self, fp, modname, basesuite, othernamemappers=None, interact=1,
verbose=None):
self.fp = fp
self.verbose = verbose
self.basesuite = basesuite
self.can_interact = interact
self.modulename = modname
self.namemappers = [CodeNameMapper(self.can_interact, self.verbose)]
if othernamemappers:
self.othernamemappers = othernamemappers[:]
else:
self.othernamemappers = []
if basesuite:
basemapper = CodeNameMapper(self.can_interact, self.verbose)
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
def findcodename(self, type, code):
while 1:
# 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(self.can_interact, self.verbose)
mapper.addmodule(m, m.__name__, 0)
self.namemappers.append(mapper)
def hasname(self, name):
for mapper in self.othernamemappers:
if mapper.hasname(name) and mapper.modulename != self.modulename:
if self.verbose:
print >>self.verbose, "Duplicate Python identifier:", name, self.modulename, mapper.modulename
return True
return False
def askdefinitionmodule(self, type, code):
if not self.can_interact:
if self.verbose:
print >>self.verbose, "** No definition for %s '%s' found" % (type, code)
return None
path = EasyDialogs.AskFileForSave(message='Where is %s %s declared?'%(type, code))
if not path: return
path, file = os.path.split(path)
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)
if self.namemappers[0].hascode('class', code):
# plural forms and such
othername, dummy, dummy = self.namemappers[0].findcodename('class', code)
if self.fp:
self.fp.write("\n%s = %s\n"%(pname, othername))
else:
if self.fp:
self.fp.write('\nclass %s(aetools.ComponentItem):\n' % pname)
self.fp.write(' """%s - %s """\n' % (ascii(name), ascii(desc)))
self.fp.write(' want = %s\n' % `code`)
self.namemappers[0].addnamecode('class', pname, code)
properties.sort()
for prop in properties:
self.compileproperty(prop)
elements.sort()
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)
if self.namemappers[0].hascode('property', code):
# plural forms and such
othername, dummy, dummy = self.namemappers[0].findcodename('property', code)
if pname == othername:
return
if self.fp:
self.fp.write("\n_Prop_%s = _Prop_%s\n"%(pname, othername))
else:
if self.hasname(pname):
pass
if self.fp:
self.fp.write("class _Prop_%s(aetools.NProperty):\n" % pname)
self.fp.write(' """%s - %s """\n' % (ascii(name), ascii(what[1])))
self.fp.write(" which = %s\n" % `code`)
self.fp.write(" want = %s\n" % `what[0]`)
self.namemappers[0].addnamecode('property', pname, code)
def compileelement(self, elem):
[code, keyform] = elem
if self.fp:
self.fp.write("# element %s as %s\n" % (`code`, keyform))
2003-04-09 10:25:43 -03:00
def fillclasspropsandelems(self, cls):
[name, code, desc, properties, elements] = cls
cname = identify(name)
if self.namemappers[0].hascode('class', code) and \
self.namemappers[0].findcodename('class', code)[0] != cname:
# This is an other name (plural or so) for something else. Skip.
if self.fp and (elements or len(properties) > 1 or (len(properties) == 1 and
properties[0][1] != 'c@#!')):
if self.verbose:
print >>self.verbose, '** Skip multiple %s of %s (code %s)' % (cname, self.namemappers[0].findcodename('class', code)[0], `code`)
raise RuntimeError, "About to skip non-empty class"
return
plist = []
elist = []
superclasses = []
for prop in properties:
[pname, pcode, what] = prop
if pcode == "c@#^":
superclasses.append(what)
if pcode == 'c@#!':
continue
pname = identify(pname)
plist.append(pname)
2003-04-09 10:25:43 -03:00
superclassnames = []
for superclass in superclasses:
superId, superDesc, dummy = superclass
superclassname, fullyqualifiedname, module = self.findcodename("class", superId)
# I don't think this is correct:
if superclassname == cname:
pass # superclassnames.append(fullyqualifiedname)
else:
superclassnames.append(superclassname)
2003-04-09 10:25:43 -03:00
if self.fp:
self.fp.write("%s._superclassnames = %s\n"%(cname, `superclassnames`))
2003-04-09 10:25:43 -03:00
for elem in elements:
[ecode, keyform] = elem
if ecode == 'c@#!':
continue
name, ename, module = self.findcodename('class', ecode)
if not name:
if self.fp:
self.fp.write("# XXXX %s element %s not found!!\n"%(cname, `ecode`))
else:
elist.append((name, ename))
plist.sort()
elist.sort()
if self.fp:
self.fp.write("%s._privpropdict = {\n"%cname)
for n in plist:
self.fp.write(" '%s' : _Prop_%s,\n"%(n, n))
self.fp.write("}\n")
self.fp.write("%s._privelemdict = {\n"%cname)
for n, fulln in elist:
self.fp.write(" '%s' : %s,\n"%(n, fulln))
self.fp.write("}\n")
def compilecomparison(self, comp):
[name, code, comment] = comp
iname = identify(name)
self.namemappers[0].addnamecode('comparison', iname, code)
if self.fp:
self.fp.write("class %s(aetools.NComparison):\n" % iname)
self.fp.write(' """%s - %s """\n' % (ascii(name), ascii(comment)))
def compileenumeration(self, enum):
[code, items] = enum
name = "_Enum_%s" % identify(code)
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)
return code
def compileenumerator(self, item):
[name, code, desc] = item
self.fp.write(" %s : %s,\t# %s\n" % (`identify(name)`, `code`, ascii(desc)))
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:
if self.fp:
self.fp.write("_Enum_%s = None # XXXX enum %s not found!!\n"%(identify(enum), ascii(enum)))
return
if module:
if self.fp:
self.fp.write("from %s import %s\n"%(module, name))
def dumpindex(self):
if not self.fp:
return
self.fp.write("\n#\n# Indices of types declared in this module\n#\n")
self.fp.write("_classdeclarations = {\n")
classlist = self.namemappers[0].getall('class')
classlist.sort()
for k, v in classlist:
self.fp.write(" %s : %s,\n" % (`k`, v))
self.fp.write("}\n")
## self.fp.write("\n_propdeclarations = {\n")
## proplist = self.namemappers[0].getall('property')
## proplist.sort()
## for k, v in proplist:
## self.fp.write(" %s : _Prop_%s,\n" % (`k`, v))
## self.fp.write("}\n")
##
## self.fp.write("\n_compdeclarations = {\n")
## complist = self.namemappers[0].getall('comparison')
## complist.sort()
## for k, v in complist:
## self.fp.write(" %s : %s,\n" % (`k`, v))
## self.fp.write("}\n")
##
## self.fp.write("\n_enumdeclarations = {\n")
## enumlist = self.namemappers[0].getall('enum')
## enumlist.sort()
## for k, v in enumlist:
## self.fp.write(" %s : %s,\n" % (`k`, v))
## self.fp.write("}\n")
def compiledata(data):
2003-04-09 10:25:43 -03:00
[type, description, flags] = data
return "%s -- %s %s" % (`type`, `description`, compiledataflags(flags))
def is_null(data):
2003-04-09 10:25:43 -03:00
return data[0] == 'null'
def is_optional(data):
2003-04-09 10:25:43 -03:00
return (data[2] & 0x8000)
def is_enum(data):
2003-04-09 10:25:43 -03:00
return (data[2] & 0x2000)
def getdatadoc(data):
2003-04-09 10:25:43 -03:00
[type, descr, flags] = data
if descr:
return ascii(descr)
if type == '****':
return 'anything'
if type == 'obj ':
return 'an AE object reference'
return "undocumented, typecode %s"%`type`
dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
def compiledataflags(flags):
2003-04-09 10:25:43 -03:00
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)
def ascii(str):
2003-04-09 10:25:43 -03:00
"""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:
rv = rv + '\\' + 'x%02.2x' % ord(c)
return rv
def identify(str):
2003-04-09 10:25:43 -03:00
"""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
"""
if not str:
return "empty_ae_name_"
rv = ''
ok = string.ascii_letters + '_'
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
if keyword.iskeyword(rv):
rv = rv + '_'
return rv
# Call the main program
if __name__ == '__main__':
2003-04-09 10:25:43 -03:00
main()
sys.exit(1)