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.
from warnings import warnpy3k
warnpy3k("In 3.x, the gensuitemodule module is removed.", stacklevel=2)
import MacOS
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
DEFAULT_STANDARD_PACKAGEFOLDER=os.path.join(_MAC_LIB_FOLDER, 'lib-scriptpackages')
def usage():
    sys.stderr.write("Usage: %s [opts] application-or-resource-file\n" % sys.argv[0])
--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)
def main():
    if len(sys.argv) > 1:
        SHORTOPTS = "rb:o:e:c:dv"
        LONGOPTS = ("resource", "base=", "output=", "edit=", "creator=", "dump", "verbose")
            opts, args = getopt.getopt(sys.argv[1:], SHORTOPTS, LONGOPTS)
        except getopt.GetoptError:
        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:
            if o in ('-c', '--creator'):
                if len(a) != 4:
                    sys.stderr.write("creator must be 4-char string\n")
                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])
        for filename in args:
            process_func(filename, output=output, basepkgname=basepkgname,
                edit_modnames=edit_modnames, creatorsignature=creatorsignature,
                dump=dump, verbose=verbose)
def main_interactive(interact=0, basepkgname='StdSuites'):
    if interact:
        # Ask for save-filename for each module
        edit_modnames = None
        # 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
    if not filename:
    if not is_scriptable(filename):
        if EasyDialogs.AskYesNoCancel(
                "Warning: application does not seem scriptable",
                yes="Continue", default=2, no="") <= 0:
        processfile(filename, edit_modnames=edit_modnames, basepkgname=basepkgname,
    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):
    """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()
        refno = macresource.open_pathname(application)
    except MacOS.Error:
        return False
    n_terminology = Count1Resources('aete') + Count1Resources('aeut') + \
        Count1Resources('scsz') + Count1Resources('osiz')
    return n_terminology > 0
def processfile_fromresource(fullname, output=None, basepkgname=None,
        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)
        resources = []
        for i in range(Count1Resources('aete')):
            res = Get1IndResource('aete', 1+i)
        for i in range(Count1Resources('aeut')):
            res = Get1IndResource('aeut', 1+i)
        if verbose:
            print >>verbose, "\nLISTING aete+aeut RESOURCES IN", repr(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()))
        if rf <> cur:
    # switch back (needed for dialogs in Python)
    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,
        edit_modnames=None, creatorsignature=None, dump=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", repr(fullname)
        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
        if launched:
            if verbose:
                print >>verbose, "Launched", fullname
        raw = aetools.unpack(aedescobj)
        if not raw:
            if verbose:
                print >>verbose, 'Unpack returned empty value:', raw
        if not raw[0].data:
            if verbose:
                print >>verbose, 'Unpack returned value without data:', raw
        aedata = raw[0]
    aete = decode(aedata.data, verbose)
    if dump:
        dumpaetelist([aete], dump)
    compileaete(aete, None, fullname, output=output, basepkgname=basepkgname,
        creatorsignature=creatorsignature, edit_modnames=edit_modnames,
def getappterminology(fullname, verbose=None):
    """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
    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]
        # Assume it is a file
        cr, tp = MacOS.GetCreatorAndType(fullname)
    # Let's talk to it and ask for its AETE
    talker = aetools.TalkTo(cr)
    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,
            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):
    import pprint
    pprint.pprint(aetelist, output)
def decode(data, verbose=None):
    """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):
    """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])
        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):
    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):
    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):
    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:', repr(c)
def getlist(f, description, getitem):
    count = getword(f)
    list = []
    for i in range(count):
        list.append(generic(getitem, f))
    return list
def alt_generic(what, f, *args):
    print "generic", repr(what), args
    res = vageneric(what, f, args)
    print '->', repr(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: %r" % (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)
def compileaete(aete, resinfo, fname, output=None, basepkgname=None,
        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):
        pathname = output
        pathname = EasyDialogs.AskFolder(message='Create and select package folder for %s'%packagename,
        output = pathname
    if not pathname:
    packagename = os.path.split(os.path.normpath(pathname))[1]
    if not basepkgname:
        basepkgname = EasyDialogs.AskFolder(message='Package folder for base suite (usually StdSuites)',
    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)
        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:
        allprecompinfo = allprecompinfo + precompinfo
        suiteinfo = suite, pathname, modname
        suitelist.append((code, modname))
    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("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('import aetools\n')
    fp.write('Error = aetools.Error\n')
    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_code_to_fullname = {\n")
    for code, modname in suitelist:
        fp.write("    '%s' : ('%s.%s', '%s'),\n"%(ascii(code), packagename, modname, modname))
    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("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
    application_class = None
    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("    %r : %s,\n" % (k, v))
                if k == 'capp':
                    application_class = v
    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 = %r\n\n"%(creatorsignature,))
        fp.write("    _moduleName = '%s'\n\n"%packagename)
        if application_class:
            fp.write("    _elemdict = %s._elemdict\n" % application_class)
            fp.write("    _propdict = %s._propdict\n" % application_class)
class SuiteCompiler:
    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',
            for old, new in self.edit_modnames:
                if old == modname:
                    modname = new
            if modname:
                self.pathname = os.path.join(self.output, modname + '.py')
                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]
            # We are not an extension.
            basemodule = None
        self.enumsneeded = {}
        for event in events:
        objc = ObjectCompiler(None, self.modname, basemodule, interact=(self.edit_modnames is None),
        for cls in classes:
        for cls in classes:
        for comp in comps:
        for enum in enums:
        for enum in self.enumsneeded.keys():
        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
        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('import aetools\n')
        fp.write('import MacOS\n\n')
        fp.write("_code = %r\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()]
            # We are not an extension.
            basemodule = None
        self.basemodule = basemodule
        self.enumsneeded = {}
        if events:
            for event in events:
            fp.write("    pass\n\n")
        objc = ObjectCompiler(fp, self.modname, basemodule, precompinfo, interact=(self.edit_modnames is None),
        for cls in classes:
        for cls in classes:
        for comp in comps:
        for enum in enums:
        for enum in self.enumsneeded.keys():
    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))
            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("        %r : %r,\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
                fp.write("_object=None, ")  # Also include if it is optional
            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]),
        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 = %r\n"% (code,))
        fp.write("        _subcode = %r\n\n"% (subcode,))
        # Do keyword name substitution
        if arguments:
            fp.write("        aetools.keysubst(_arguments, self._argmap_%s)\n"%funcname)
            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")
            fp.write("        if _no_object is not None: raise TypeError, 'No direct arg expected'\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, %r, _Enum_%s)\n" %
                        (kname, identify(ename)))
                    self.enumsneeded[ename] = 1
        # 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")
    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:
    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)
            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:
    def __init__(self, fp, modname, basesuite, othernamemappers=None, interact=1,
        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[:]
            self.othernamemappers = []
        if basesuite:
            basemapper = CodeNameMapper(self.can_interact, self.verbose)
            basemapper.addmodule(basesuite, '', 1)
    def getprecompinfo(self, modname):
        list = []
        for mapper in self.namemappers:
            emapper = mapper.prepareforexport(modname)
            if 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):
                    if self.fp:
                        self.fp.write("import %s\n"%mapper.modulename)
                # 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)
                    m = None
                if not m: return None, None, None
                mapper = CodeNameMapper(self.can_interact, self.verbose)
                mapper.addmodule(m, m.__name__, 0)
    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))
            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 = %r\n' % (code,))
        self.namemappers[0].addnamecode('class', pname, code)
        is_application_class = (code == 'capp')
        for prop in properties:
            self.compileproperty(prop, is_application_class)
        for elem in elements:
    def compileproperty(self, prop, is_application_class=False):
        [name, code, what] = prop
        if code == 'c@#!':
            # Something silly with plurals. Skip it.
        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:
            if self.fp:
                self.fp.write("\n_Prop_%s = _Prop_%s\n"%(pname, othername))
            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 = %r\n" % (code,))
                self.fp.write("    want = %r\n" % (what[0],))
        self.namemappers[0].addnamecode('property', pname, code)
        if is_application_class and self.fp:
            self.fp.write("%s = _Prop_%s()\n" % (pname, pname))
    def compileelement(self, elem):
        [code, keyform] = elem
        if self.fp:
            self.fp.write("#        element %r as %s\n" % (code, keyform))
    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 %r)' % (cname, self.namemappers[0].findcodename('class', code)[0], code)
                raise RuntimeError, "About to skip non-empty class"
        plist = []
        elist = []
        superclasses = []
        for prop in properties:
            [pname, pcode, what] = prop
            if pcode == "c@#^":
            if pcode == 'c@#!':
            pname = identify(pname)
        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)
        if self.fp:
            self.fp.write("%s._superclassnames = %r\n"%(cname, superclassnames))
        for elem in elements:
            [ecode, keyform] = elem
            if ecode == 'c@#!':
            name, ename, module = self.findcodename('class', ecode)
            if not name:
                if self.fp:
                    self.fp.write("# XXXX %s element %r not found!!\n"%(cname, ecode))
                elist.append((name, ename))
        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("%s._privelemdict = {\n"%cname)
            for n, fulln in elist:
                self.fp.write("    '%s' : %s,\n"%(n, fulln))
    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.namemappers[0].addnamecode('enum', name, code)
        return code
    def compileenumerator(self, item):
        [name, code, desc] = item
        self.fp.write("    %r : %r,\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)))
        if module:
            if self.fp:
                self.fp.write("from %s import %s\n"%(module, name))
    def dumpindex(self):
        if not self.fp:
        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')
        for k, v in classlist:
            self.fp.write("    %r : %s,\n" % (k, v))
        self.fp.write("\n_propdeclarations = {\n")
        proplist = self.namemappers[0].getall('property')
        for k, v in proplist:
            self.fp.write("    %r : _Prop_%s,\n" % (k, v))
        self.fp.write("\n_compdeclarations = {\n")
        complist = self.namemappers[0].getall('comparison')
        for k, v in complist:
            self.fp.write("    %r : %s,\n" % (k, v))
        self.fp.write("\n_enumdeclarations = {\n")
        enumlist = self.namemappers[0].getall('enum')
        for k, v in enumlist:
            self.fp.write("    %r : %s,\n" % (k, v))
def compiledata(data):
    [type, description, flags] = data
    return "%r -- %r %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:
        return ascii(descr)
    if type == '****':
        return 'anything'
    if type == 'obj ':
        return 'an AE object reference'
    return "undocumented, typecode %r"%(type,)
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():
    return '[%s]' % string.join(bits)
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
            rv = rv + '\\' + 'x%02.2x' % ord(c)
    return rv
def identify(str):
    """Turn any string into an identifier:
    - replace space by _
    - replace other illegal chars by _xx_ (hex code)
    - append _ 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 + '_'
            rv = rv + '_%02.2x_'%ord(c)
        ok = ok2
    if keyword.iskeyword(rv):
        rv = rv + '_'
    return rv
# Call the main program
if __name__ == '__main__':