2 gensuitemodule - Generate an AE suite module from an aete/aeut resource
6 Reading and understanding this code is left as an exercise to the reader.
9 from warnings
import warnpy3k
10 warnpy3k("In 3.x, the gensuitemodule module is removed.", stacklevel
=2)
22 import distutils
.sysconfig
24 from Carbon
.Res
import *
30 _MAC_LIB_FOLDER
=os
.path
.dirname(aetools
.__file
__)
31 DEFAULT_STANDARD_PACKAGEFOLDER
=os
.path
.join(_MAC_LIB_FOLDER
, 'lib-scriptpackages')
32 DEFAULT_USER_PACKAGEFOLDER
=distutils
.sysconfig
.get_python_lib()
35 sys
.stderr
.write("Usage: %s [opts] application-or-resource-file\n" % sys
.argv
[0])
36 sys
.stderr
.write("""Options:
37 --output pkgdir Pathname of the output package (short: -o)
38 --resource Parse resource file in stead of launching application (-r)
39 --base package Use another base package in stead of default StdSuites (-b)
40 --edit old=new Edit suite names, use empty new to skip a suite (-e)
41 --creator code Set creator code for package (-c)
42 --dump Dump aete resource to stdout in stead of creating module (-d)
43 --verbose Tell us what happens (-v)
49 SHORTOPTS
= "rb:o:e:c:dv"
50 LONGOPTS
= ("resource", "base=", "output=", "edit=", "creator=", "dump", "verbose")
52 opts
, args
= getopt
.getopt(sys
.argv
[1:], SHORTOPTS
, LONGOPTS
)
53 except getopt
.GetoptError
:
56 process_func
= processfile
57 basepkgname
= 'StdSuites'
60 creatorsignature
= None
65 if o
in ('-r', '--resource'):
66 process_func
= processfile_fromresource
67 if o
in ('-b', '--base'):
69 if o
in ('-o', '--output'):
71 if o
in ('-e', '--edit'):
75 edit_modnames
.append(split
)
76 if o
in ('-c', '--creator'):
78 sys
.stderr
.write("creator must be 4-char string\n")
81 if o
in ('-d', '--dump'):
83 if o
in ('-v', '--verbose'):
87 if output
and len(args
) > 1:
88 sys
.stderr
.write("%s: cannot specify --output with multiple inputs\n" % sys
.argv
[0])
92 process_func(filename
, output
=output
, basepkgname
=basepkgname
,
93 edit_modnames
=edit_modnames
, creatorsignature
=creatorsignature
,
94 dump
=dump
, verbose
=verbose
)
98 def main_interactive(interact
=0, basepkgname
='StdSuites'):
100 # Ask for save-filename for each module
103 # Use default filenames for each module
105 appsfolder
= Carbon
.Folder
.FSFindFolder(-32765, 'apps', 0)
106 filename
= EasyDialogs
.AskFileForOpen(
107 message
='Select scriptable application',
108 dialogOptionFlags
=0x1056, # allow selection of .app bundles
109 defaultLocation
=appsfolder
)
112 if not is_scriptable(filename
):
113 if EasyDialogs
.AskYesNoCancel(
114 "Warning: application does not seem scriptable",
115 yes
="Continue", default
=2, no
="") <= 0:
118 processfile(filename
, edit_modnames
=edit_modnames
, basepkgname
=basepkgname
,
120 except MacOS
.Error
, arg
:
121 print "Error getting terminology:", arg
122 print "Retry, manually parsing resources"
123 processfile_fromresource(filename
, edit_modnames
=edit_modnames
,
124 basepkgname
=basepkgname
, verbose
=sys
.stderr
)
126 def is_scriptable(application
):
127 """Return true if the application is scriptable"""
128 if os
.path
.isdir(application
):
129 plistfile
= os
.path
.join(application
, 'Contents', 'Info.plist')
130 if not os
.path
.exists(plistfile
):
132 plist
= plistlib
.Plist
.fromFile(plistfile
)
133 return plist
.get('NSAppleScriptEnabled', False)
134 # If it is a file test for an aete/aeut resource.
137 refno
= macresource
.open_pathname(application
)
141 n_terminology
= Count1Resources('aete') + Count1Resources('aeut') + \
142 Count1Resources('scsz') + Count1Resources('osiz')
145 return n_terminology
> 0
147 def processfile_fromresource(fullname
, output
=None, basepkgname
=None,
148 edit_modnames
=None, creatorsignature
=None, dump
=None, verbose
=None):
149 """Process all resources in a single file"""
150 if not is_scriptable(fullname
) and verbose
:
151 print >>verbose
, "Warning: app does not seem scriptable: %s" % fullname
154 print >>verbose
, "Processing", fullname
155 rf
= macresource
.open_pathname(fullname
)
159 for i
in range(Count1Resources('aete')):
160 res
= Get1IndResource('aete', 1+i
)
161 resources
.append(res
)
162 for i
in range(Count1Resources('aeut')):
163 res
= Get1IndResource('aeut', 1+i
)
164 resources
.append(res
)
166 print >>verbose
, "\nLISTING aete+aeut RESOURCES IN", repr(fullname
)
168 for res
in resources
:
170 print >>verbose
, "decoding", res
.GetResInfo(), "..."
172 aete
= decode(data
, verbose
)
173 aetelist
.append((aete
, res
.GetResInfo()))
178 # switch back (needed for dialogs in Python)
181 dumpaetelist(aetelist
, dump
)
182 compileaetelist(aetelist
, fullname
, output
=output
,
183 basepkgname
=basepkgname
, edit_modnames
=edit_modnames
,
184 creatorsignature
=creatorsignature
, verbose
=verbose
)
186 def processfile(fullname
, output
=None, basepkgname
=None,
187 edit_modnames
=None, creatorsignature
=None, dump
=None,
189 """Ask an application for its terminology and process that"""
190 if not is_scriptable(fullname
) and verbose
:
191 print >>verbose
, "Warning: app does not seem scriptable: %s" % fullname
193 print >>verbose
, "\nASKING FOR aete DICTIONARY IN", repr(fullname
)
195 aedescobj
, launched
= OSATerminology
.GetAppTerminology(fullname
)
196 except MacOS
.Error
, arg
:
197 if arg
[0] in (-1701, -192): # errAEDescNotFound, resNotFound
199 print >>verbose
, "GetAppTerminology failed with errAEDescNotFound/resNotFound, trying manually"
200 aedata
, sig
= getappterminology(fullname
, verbose
=verbose
)
201 if not creatorsignature
:
202 creatorsignature
= sig
208 print >>verbose
, "Launched", fullname
209 raw
= aetools
.unpack(aedescobj
)
212 print >>verbose
, 'Unpack returned empty value:', raw
216 print >>verbose
, 'Unpack returned value without data:', raw
219 aete
= decode(aedata
.data
, verbose
)
221 dumpaetelist([aete
], dump
)
223 compileaete(aete
, None, fullname
, output
=output
, basepkgname
=basepkgname
,
224 creatorsignature
=creatorsignature
, edit_modnames
=edit_modnames
,
227 def getappterminology(fullname
, verbose
=None):
228 """Get application terminology by sending an AppleEvent"""
229 # First check that we actually can send AppleEvents
230 if not MacOS
.WMAvailable():
231 raise RuntimeError, "Cannot send AppleEvents, no access to window manager"
232 # Next, a workaround for a bug in MacOS 10.2: sending events will hang unless
233 # you have created an event loop first.
235 Carbon
.Evt
.WaitNextEvent(0,0)
236 if os
.path
.isdir(fullname
):
237 # Now get the signature of the application, hoping it is a bundle
238 pkginfo
= os
.path
.join(fullname
, 'Contents', 'PkgInfo')
239 if not os
.path
.exists(pkginfo
):
240 raise RuntimeError, "No PkgInfo file found"
241 tp_cr
= open(pkginfo
, 'rb').read()
244 # Assume it is a file
245 cr
, tp
= MacOS
.GetCreatorAndType(fullname
)
246 # Let's talk to it and ask for its AETE
247 talker
= aetools
.TalkTo(cr
)
250 except (MacOS
.Error
, aetools
.Error
), arg
:
252 print >>verbose
, 'Warning: start() failed, continuing anyway:', arg
253 reply
= talker
.send("ascr", "gdte")
254 #reply2 = talker.send("ascr", "gdut")
255 # Now pick the bits out of the return that we need.
256 return reply
[1]['----'], cr
259 def compileaetelist(aetelist
, fullname
, output
=None, basepkgname
=None,
260 edit_modnames
=None, creatorsignature
=None, verbose
=None):
261 for aete
, resinfo
in aetelist
:
262 compileaete(aete
, resinfo
, fullname
, output
=output
,
263 basepkgname
=basepkgname
, edit_modnames
=edit_modnames
,
264 creatorsignature
=creatorsignature
, verbose
=verbose
)
266 def dumpaetelist(aetelist
, output
):
268 pprint
.pprint(aetelist
, output
)
270 def decode(data
, verbose
=None):
271 """Decode a resource into a python data structure"""
272 f
= StringIO
.StringIO(data
)
273 aete
= generic(getaete
, f
)
274 aete
= simplify(aete
)
276 unprocessed
= len(f
.read())
278 if unprocessed
and verbose
:
279 verbose
.write("%d processed + %d unprocessed = %d total\n" %
280 (processed
, unprocessed
, total
))
284 """Recursively replace singleton tuples by their constituent item"""
285 if type(item
) is types
.ListType
:
286 return map(simplify
, item
)
287 elif type(item
) == types
.TupleType
and len(item
) == 2:
288 return simplify(item
[1])
293 # Here follows the aete resource decoder.
294 # It is presented bottom-up instead of top-down because there are direct
295 # references to the lower-level part-decoders from the high-level part-decoders.
297 def getbyte(f
, *args
):
300 raise EOFError, 'in getbyte' + str(args
)
303 def getword(f
, *args
):
307 raise EOFError, 'in getword' + str(args
)
308 return (ord(s
[0])<<8) |
ord(s
[1])
310 def getlong(f
, *args
):
314 raise EOFError, 'in getlong' + str(args
)
315 return (ord(s
[0])<<24) |
(ord(s
[1])<<16) |
(ord(s
[2])<<8) |
ord(s
[3])
317 def getostype(f
, *args
):
321 raise EOFError, 'in getostype' + str(args
)
324 def getpstr(f
, *args
):
327 raise EOFError, 'in getpstr[1]' + str(args
)
329 if nbytes
== 0: return ''
332 raise EOFError, 'in getpstr[2]' + str(args
)
339 ## print align:', repr(c)
341 def getlist(f
, description
, getitem
):
344 for i
in range(count
):
345 list.append(generic(getitem
, f
))
349 def alt_generic(what
, f
, *args
):
350 print "generic", repr(what
), args
351 res
= vageneric(what
, f
, args
)
352 print '->', repr(res
)
355 def generic(what
, f
, *args
):
356 if type(what
) == types
.FunctionType
:
357 return apply(what
, (f
,) + args
)
358 if type(what
) == types
.ListType
:
361 item
= apply(generic
, thing
[:1] + (f
,) + thing
[1:])
362 record
.append((thing
[1], item
))
364 return "BAD GENERIC ARGS: %r" % (what
,)
368 (getpstr
, "description"),
373 (getostype
, "keyword"),
378 (getpstr
, "description"),
379 (getostype
, "suite code"),
380 (getostype
, "event code"),
381 (getdata
, "returns"),
382 (getdata
, "accepts"),
383 (getlist
, "optional arguments", getargument
)
392 (getlist
, "keyform", getostype
)
396 (getostype
, "class code"),
397 (getpstr
, "description"),
398 (getlist
, "properties", getproperty
),
399 (getlist
, "elements", getelement
)
402 (getpstr
, "operator name"),
403 (getostype
, "operator ID"),
404 (getpstr
, "operator comment"),
407 (getpstr
, "enumerator name"),
408 (getostype
, "enumerator ID"),
409 (getpstr
, "enumerator comment")
412 (getostype
, "enumeration ID"),
413 (getlist
, "enumerator", getenumerator
)
416 (getpstr
, "suite name"),
417 (getpstr
, "suite description"),
418 (getostype
, "suite ID"),
419 (getword
, "suite level"),
420 (getword
, "suite version"),
421 (getlist
, "events", getevent
),
422 (getlist
, "classes", getclass
),
423 (getlist
, "comparisons", getcomparison
),
424 (getlist
, "enumerations", getenumeration
)
427 (getword
, "major/minor version in BCD"),
428 (getword
, "language code"),
429 (getword
, "script code"),
430 (getlist
, "suites", getsuite
)
433 def compileaete(aete
, resinfo
, fname
, output
=None, basepkgname
=None,
434 edit_modnames
=None, creatorsignature
=None, verbose
=None):
435 """Generate code for a full aete resource. fname passed for doc purposes"""
436 [version
, language
, script
, suites
] = aete
437 major
, minor
= divmod(version
, 256)
438 if not creatorsignature
:
439 creatorsignature
, dummy
= MacOS
.GetCreatorAndType(fname
)
440 packagename
= identify(os
.path
.splitext(os
.path
.basename(fname
))[0])
442 packagename
= packagename
+'_lang%d'%language
444 packagename
= packagename
+'_script%d'%script
445 if len(packagename
) > 27:
446 packagename
= packagename
[:27]
448 # XXXX Put this in site-packages if it isn't a full pathname?
449 if not os
.path
.exists(output
):
453 pathname
= EasyDialogs
.AskFolder(message
='Create and select package folder for %s'%packagename
,
454 defaultLocation
=DEFAULT_USER_PACKAGEFOLDER
)
458 packagename
= os
.path
.split(os
.path
.normpath(pathname
))[1]
460 basepkgname
= EasyDialogs
.AskFolder(message
='Package folder for base suite (usually StdSuites)',
461 defaultLocation
=DEFAULT_STANDARD_PACKAGEFOLDER
)
463 dirname
, basepkgname
= os
.path
.split(os
.path
.normpath(basepkgname
))
464 if dirname
and not dirname
in sys
.path
:
465 sys
.path
.insert(0, dirname
)
466 basepackage
= __import__(basepkgname
)
473 compiler
= SuiteCompiler(suite
, basepackage
, output
, edit_modnames
, verbose
)
474 code
, modname
, precompinfo
= compiler
.precompilesuite()
477 allprecompinfo
= allprecompinfo
+ precompinfo
478 suiteinfo
= suite
, pathname
, modname
479 suitelist
.append((code
, modname
))
480 allsuites
.append(compiler
)
481 for compiler
in allsuites
:
482 compiler
.compilesuite(major
, minor
, language
, script
, fname
, allprecompinfo
)
483 initfilename
= os
.path
.join(output
, '__init__.py')
484 fp
= open(initfilename
, 'w')
485 MacOS
.SetCreatorAndType(initfilename
, 'Pyth', 'TEXT')
487 fp
.write("Package generated from %s\n"%ascii(fname
))
489 fp
.write("Resource %s resid %d %s\n"%(ascii(resinfo
[1]), resinfo
[0], ascii(resinfo
[2])))
491 fp
.write('import aetools\n')
492 fp
.write('Error = aetools.Error\n')
494 for code
, modname
in suitelist
:
495 fp
.write("import %s\n" % modname
)
496 fp
.write("\n\n_code_to_module = {\n")
497 for code
, modname
in suitelist
:
498 fp
.write(" '%s' : %s,\n"%(ascii(code
), modname
))
500 fp
.write("\n\n_code_to_fullname = {\n")
501 for code
, modname
in suitelist
:
502 fp
.write(" '%s' : ('%s.%s', '%s'),\n"%(ascii(code
), packagename
, modname
, modname
))
504 for code
, modname
in suitelist
:
505 fp
.write("from %s import *\n"%modname
)
507 # Generate property dicts and element dicts for all types declared in this module
508 fp
.write("\ndef getbaseclasses(v):\n")
509 fp
.write(" if not getattr(v, '_propdict', None):\n")
510 fp
.write(" v._propdict = {}\n")
511 fp
.write(" v._elemdict = {}\n")
512 fp
.write(" for superclassname in getattr(v, '_superclassnames', []):\n")
513 fp
.write(" superclass = eval(superclassname)\n")
514 fp
.write(" getbaseclasses(superclass)\n")
515 fp
.write(" v._propdict.update(getattr(superclass, '_propdict', {}))\n")
516 fp
.write(" v._elemdict.update(getattr(superclass, '_elemdict', {}))\n")
517 fp
.write(" v._propdict.update(getattr(v, '_privpropdict', {}))\n")
518 fp
.write(" v._elemdict.update(getattr(v, '_privelemdict', {}))\n")
520 fp
.write("import StdSuites\n")
521 allprecompinfo
.sort()
523 fp
.write("\n#\n# Set property and element dictionaries now that all classes have been defined\n#\n")
524 for codenamemapper
in allprecompinfo
:
525 for k
, v
in codenamemapper
.getall('class'):
526 fp
.write("getbaseclasses(%s)\n" % v
)
528 # Generate a code-to-name mapper for all of the types (classes) declared in this module
529 application_class
= None
531 fp
.write("\n#\n# Indices of types declared in this module\n#\n")
532 fp
.write("_classdeclarations = {\n")
533 for codenamemapper
in allprecompinfo
:
534 for k
, v
in codenamemapper
.getall('class'):
535 fp
.write(" %r : %s,\n" % (k
, v
))
537 application_class
= v
542 fp
.write("\n\nclass %s(%s_Events"%(packagename
, suitelist
[0][1]))
543 for code
, modname
in suitelist
[1:]:
544 fp
.write(",\n %s_Events"%modname
)
545 fp
.write(",\n aetools.TalkTo):\n")
546 fp
.write(" _signature = %r\n\n"%(creatorsignature
,))
547 fp
.write(" _moduleName = '%s'\n\n"%packagename
)
548 if application_class
:
549 fp
.write(" _elemdict = %s._elemdict\n" % application_class
)
550 fp
.write(" _propdict = %s._propdict\n" % application_class
)
554 def __init__(self
, suite
, basepackage
, output
, edit_modnames
, verbose
):
556 self
.basepackage
= basepackage
557 self
.edit_modnames
= edit_modnames
559 self
.verbose
= verbose
561 # Set by precompilesuite
565 # Set by compilesuite
567 self
.basemodule
= None
568 self
.enumsneeded
= {}
570 def precompilesuite(self
):
571 """Parse a single suite without generating the output. This step is needed
572 so we can resolve recursive references by suites to enums/comps/etc declared
574 [name
, desc
, code
, level
, version
, events
, classes
, comps
, enums
] = self
.suite
576 modname
= identify(name
)
577 if len(modname
) > 28:
578 modname
= modname
[:27]
579 if self
.edit_modnames
is None:
580 self
.pathname
= EasyDialogs
.AskFileForSave(message
='Python output file',
581 savedFileName
=modname
+'.py')
583 for old
, new
in self
.edit_modnames
:
587 self
.pathname
= os
.path
.join(self
.output
, modname
+ '.py')
590 if not self
.pathname
:
591 return None, None, None
593 self
.modname
= os
.path
.splitext(os
.path
.split(self
.pathname
)[1])[0]
595 if self
.basepackage
and self
.basepackage
._code
_to
_module
.has_key(code
):
596 # We are an extension of a baseclass (usually an application extending
597 # Standard_Suite or so). Import everything from our base module
598 basemodule
= self
.basepackage
._code
_to
_module
[code
]
600 # We are not an extension.
603 self
.enumsneeded
= {}
605 self
.findenumsinevent(event
)
607 objc
= ObjectCompiler(None, self
.modname
, basemodule
, interact
=(self
.edit_modnames
is None),
608 verbose
=self
.verbose
)
610 objc
.compileclass(cls
)
612 objc
.fillclasspropsandelems(cls
)
614 objc
.compilecomparison(comp
)
616 objc
.compileenumeration(enum
)
618 for enum
in self
.enumsneeded
.keys():
619 objc
.checkforenum(enum
)
623 precompinfo
= objc
.getprecompinfo(self
.modname
)
625 return code
, self
.modname
, precompinfo
627 def compilesuite(self
, major
, minor
, language
, script
, fname
, precompinfo
):
628 """Generate code for a single suite"""
629 [name
, desc
, code
, level
, version
, events
, classes
, comps
, enums
] = self
.suite
630 # Sort various lists, so re-generated source is easier compared
631 def class_sorter(k1
, k2
):
632 """Sort classes by code, and make sure main class sorts before synonyms"""
633 # [name, code, desc, properties, elements] = cls
634 if k1
[1] < k2
[1]: return -1
635 if k1
[1] > k2
[1]: return 1
636 if not k2
[3] or k2
[3][0][1] == 'c@#!':
637 # This is a synonym, the other one is better
639 if not k1
[3] or k1
[3][0][1] == 'c@#!':
640 # This is a synonym, the other one is better
645 classes
.sort(class_sorter
)
649 self
.fp
= fp
= open(self
.pathname
, 'w')
650 MacOS
.SetCreatorAndType(self
.pathname
, 'Pyth', 'TEXT')
652 fp
.write('"""Suite %s: %s\n' % (ascii(name
), ascii(desc
)))
653 fp
.write("Level %d, version %d\n\n" % (level
, version
))
654 fp
.write("Generated from %s\n"%ascii(fname
))
655 fp
.write("AETE/AEUT resource version %d/%d, language %d, script %d\n" % \
656 (major
, minor
, language
, script
))
659 fp
.write('import aetools\n')
660 fp
.write('import MacOS\n\n')
661 fp
.write("_code = %r\n\n"% (code
,))
662 if self
.basepackage
and self
.basepackage
._code
_to
_module
.has_key(code
):
663 # We are an extension of a baseclass (usually an application extending
664 # Standard_Suite or so). Import everything from our base module
665 fp
.write('from %s import *\n'%self
.basepackage
._code
_to
_fullname
[code
][0])
666 basemodule
= self
.basepackage
._code
_to
_module
[code
]
667 elif self
.basepackage
and self
.basepackage
._code
_to
_module
.has_key(code
.lower()):
668 # This is needed by CodeWarrior and some others.
669 fp
.write('from %s import *\n'%self
.basepackage
._code
_to
_fullname
[code
.lower()][0])
670 basemodule
= self
.basepackage
._code
_to
_module
[code
.lower()]
672 # We are not an extension.
674 self
.basemodule
= basemodule
675 self
.compileclassheader()
677 self
.enumsneeded
= {}
680 self
.compileevent(event
)
682 fp
.write(" pass\n\n")
684 objc
= ObjectCompiler(fp
, self
.modname
, basemodule
, precompinfo
, interact
=(self
.edit_modnames
is None),
685 verbose
=self
.verbose
)
687 objc
.compileclass(cls
)
689 objc
.fillclasspropsandelems(cls
)
691 objc
.compilecomparison(comp
)
693 objc
.compileenumeration(enum
)
695 for enum
in self
.enumsneeded
.keys():
696 objc
.checkforenum(enum
)
700 def compileclassheader(self
):
701 """Generate class boilerplate"""
702 classname
= '%s_Events'%self
.modname
704 modshortname
= string
.split(self
.basemodule
.__name
__, '.')[-1]
705 baseclassname
= '%s_Events'%modshortname
706 self
.fp
.write("class %s(%s):\n\n"%(classname
, baseclassname
))
708 self
.fp
.write("class %s:\n\n"%classname
)
710 def compileevent(self
, event
):
711 """Generate code for a single event"""
712 [name
, desc
, code
, subcode
, returns
, accepts
, arguments
] = event
714 funcname
= identify(name
)
716 # generate name->keyword map
719 fp
.write(" _argmap_%s = {\n"%funcname
)
721 fp
.write(" %r : %r,\n"%(identify(a
[0]), a
[1]))
725 # Generate function header
727 has_arg
= (not is_null(accepts
))
728 opt_arg
= (has_arg
and is_optional(accepts
))
730 fp
.write(" def %s(self, "%funcname
)
733 fp
.write("_object, ") # Include direct object, if it has one
735 fp
.write("_object=None, ") # Also include if it is optional
737 fp
.write("_no_object=None, ") # For argument checking
738 fp
.write("_attributes={}, **_arguments):\n") # include attribute dict and args
740 # Generate doc string (important, since it may be the only
741 # available documentation, due to our name-remaping)
743 fp
.write(' """%s: %s\n'%(ascii(name
), ascii(desc
)))
745 fp
.write(" Required argument: %s\n"%getdatadoc
(accepts
))
747 fp
.write(" Optional argument: %s\n"%getdatadoc
(accepts
))
748 for arg
in arguments
:
749 fp
.write(" Keyword argument %s: %s\n"%(identify(arg
[0]),
751 fp
.write(" Keyword argument _attributes: AppleEvent attribute dictionary\n")
752 if not is_null(returns
):
753 fp
.write(" Returns: %s\n"%getdatadoc
(returns
))
756 # Fiddle the args so everything ends up in 'arguments' dictionary
758 fp
.write(" _code = %r\n"% (code
,))
759 fp
.write(" _subcode = %r\n\n"% (subcode
,))
761 # Do keyword name substitution
764 fp
.write(" aetools.keysubst(_arguments, self._argmap_%s)\n"%funcname
)
766 fp
.write(" if _arguments: raise TypeError, 'No optional args expected'\n")
768 # Stuff required arg (if there is one) into arguments
771 fp
.write(" _arguments['----'] = _object\n")
773 fp
.write(" if _object:\n")
774 fp
.write(" _arguments['----'] = _object\n")
776 fp
.write(" if _no_object is not None: raise TypeError, 'No direct arg expected'\n")
779 # Do enum-name substitution
786 fp
.write(" aetools.enumsubst(_arguments, %r, _Enum_%s)\n" %
787 (kname
, identify(ename
)))
788 self
.enumsneeded
[ename
] = 1
793 fp
.write(" _reply, _arguments, _attributes = self.send(_code, _subcode,\n")
794 fp
.write(" _arguments, _attributes)\n")
798 fp
.write(" if _arguments.get('errn', 0):\n")
799 fp
.write(" raise aetools.Error, aetools.decodeerror(_arguments)\n")
800 fp
.write(" # XXXX Optionally decode result\n")
804 fp
.write(" if _arguments.has_key('----'):\n")
806 fp
.write(" # XXXX Should do enum remapping here...\n")
807 fp
.write(" return _arguments['----']\n")
810 def findenumsinevent(self
, event
):
811 """Find all enums for a single event"""
812 [name
, desc
, code
, subcode
, returns
, accepts
, arguments
] = event
817 self
.enumsneeded
[ename
] = 1
820 # This class stores the code<->name translations for a single module. It is used
821 # to keep the information while we're compiling the module, but we also keep these objects
822 # around so if one suite refers to, say, an enum in another suite we know where to
823 # find it. Finally, if we really can't find a code, the user can add modules by
826 class CodeNameMapper
:
828 def __init__(self
, interact
=1, verbose
=None):
841 self
.modulename
= None
842 self
.star_imported
= 0
843 self
.can_interact
= interact
844 self
.verbose
= verbose
846 def addnamecode(self
, type, name
, code
):
847 self
.name2code
[type][name
] = code
848 if not self
.code2name
[type].has_key(code
):
849 self
.code2name
[type][code
] = name
851 def hasname(self
, name
):
852 for dict in self
.name2code
.values():
853 if dict.has_key(name
):
857 def hascode(self
, type, code
):
858 return self
.code2name
[type].has_key(code
)
860 def findcodename(self
, type, code
):
861 if not self
.hascode(type, code
):
862 return None, None, None
863 name
= self
.code2name
[type][code
]
864 if self
.modulename
and not self
.star_imported
:
865 qualname
= '%s.%s'%(self
.modulename
, name
)
868 return name
, qualname
, self
.modulename
870 def getall(self
, type):
871 return self
.code2name
[type].items()
873 def addmodule(self
, module
, name
, star_imported
):
874 self
.modulename
= name
875 self
.star_imported
= star_imported
876 for code
, name
in module
._propdeclarations
.items():
877 self
.addnamecode('property', name
, code
)
878 for code
, name
in module
._classdeclarations
.items():
879 self
.addnamecode('class', name
, code
)
880 for code
in module
._enumdeclarations
.keys():
881 self
.addnamecode('enum', '_Enum_'+identify(code
), code
)
882 for code
, name
in module
._compdeclarations
.items():
883 self
.addnamecode('comparison', name
, code
)
885 def prepareforexport(self
, name
=None):
886 if not self
.modulename
:
887 self
.modulename
= name
890 class ObjectCompiler
:
891 def __init__(self
, fp
, modname
, basesuite
, othernamemappers
=None, interact
=1,
894 self
.verbose
= verbose
895 self
.basesuite
= basesuite
896 self
.can_interact
= interact
897 self
.modulename
= modname
898 self
.namemappers
= [CodeNameMapper(self
.can_interact
, self
.verbose
)]
900 self
.othernamemappers
= othernamemappers
[:]
902 self
.othernamemappers
= []
904 basemapper
= CodeNameMapper(self
.can_interact
, self
.verbose
)
905 basemapper
.addmodule(basesuite
, '', 1)
906 self
.namemappers
.append(basemapper
)
908 def getprecompinfo(self
, modname
):
910 for mapper
in self
.namemappers
:
911 emapper
= mapper
.prepareforexport(modname
)
916 def findcodename(self
, type, code
):
918 # First try: check whether we already know about this code.
919 for mapper
in self
.namemappers
:
920 if mapper
.hascode(type, code
):
921 return mapper
.findcodename(type, code
)
922 # Second try: maybe one of the other modules knows about it.
923 for mapper
in self
.othernamemappers
:
924 if mapper
.hascode(type, code
):
925 self
.othernamemappers
.remove(mapper
)
926 self
.namemappers
.append(mapper
)
928 self
.fp
.write("import %s\n"%mapper
.modulename
)
931 # If all this has failed we ask the user for a guess on where it could
934 m
= self
.askdefinitionmodule(type, code
)
937 if not m
: return None, None, None
938 mapper
= CodeNameMapper(self
.can_interact
, self
.verbose
)
939 mapper
.addmodule(m
, m
.__name
__, 0)
940 self
.namemappers
.append(mapper
)
942 def hasname(self
, name
):
943 for mapper
in self
.othernamemappers
:
944 if mapper
.hasname(name
) and mapper
.modulename
!= self
.modulename
:
946 print >>self
.verbose
, "Duplicate Python identifier:", name
, self
.modulename
, mapper
.modulename
950 def askdefinitionmodule(self
, type, code
):
951 if not self
.can_interact
:
953 print >>self
.verbose
, "** No definition for %s '%s' found" % (type, code
)
955 path
= EasyDialogs
.AskFileForSave(message
='Where is %s %s declared?'%(type, code
))
957 path
, file = os
.path
.split(path
)
958 modname
= os
.path
.splitext(file)[0]
959 if not path
in sys
.path
:
960 sys
.path
.insert(0, path
)
961 m
= __import__(modname
)
962 self
.fp
.write("import %s\n"%modname
)
965 def compileclass(self
, cls
):
966 [name
, code
, desc
, properties
, elements
] = cls
967 pname
= identify(name
)
968 if self
.namemappers
[0].hascode('class', code
):
969 # plural forms and such
970 othername
, dummy
, dummy
= self
.namemappers
[0].findcodename('class', code
)
972 self
.fp
.write("\n%s = %s\n"%(pname
, othername
))
975 self
.fp
.write('\nclass %s(aetools.ComponentItem):\n' % pname
)
976 self
.fp
.write(' """%s - %s """\n' % (ascii(name
), ascii(desc
)))
977 self
.fp
.write(' want = %r\n' % (code
,))
978 self
.namemappers
[0].addnamecode('class', pname
, code
)
979 is_application_class
= (code
== 'capp')
981 for prop
in properties
:
982 self
.compileproperty(prop
, is_application_class
)
984 for elem
in elements
:
985 self
.compileelement(elem
)
987 def compileproperty(self
, prop
, is_application_class
=False):
988 [name
, code
, what
] = prop
990 # Something silly with plurals. Skip it.
992 pname
= identify(name
)
993 if self
.namemappers
[0].hascode('property', code
):
994 # plural forms and such
995 othername
, dummy
, dummy
= self
.namemappers
[0].findcodename('property', code
)
996 if pname
== othername
:
999 self
.fp
.write("\n_Prop_%s = _Prop_%s\n"%(pname
, othername
))
1002 self
.fp
.write("class _Prop_%s(aetools.NProperty):\n" % pname
)
1003 self
.fp
.write(' """%s - %s """\n' % (ascii(name
), ascii(what
[1])))
1004 self
.fp
.write(" which = %r\n" % (code
,))
1005 self
.fp
.write(" want = %r\n" % (what
[0],))
1006 self
.namemappers
[0].addnamecode('property', pname
, code
)
1007 if is_application_class
and self
.fp
:
1008 self
.fp
.write("%s = _Prop_%s()\n" % (pname
, pname
))
1010 def compileelement(self
, elem
):
1011 [code
, keyform
] = elem
1013 self
.fp
.write("# element %r as %s\n" % (code
, keyform
))
1015 def fillclasspropsandelems(self
, cls
):
1016 [name
, code
, desc
, properties
, elements
] = cls
1017 cname
= identify(name
)
1018 if self
.namemappers
[0].hascode('class', code
) and \
1019 self
.namemappers
[0].findcodename('class', code
)[0] != cname
:
1020 # This is an other name (plural or so) for something else. Skip.
1021 if self
.fp
and (elements
or len(properties
) > 1 or (len(properties
) == 1 and
1022 properties
[0][1] != 'c@#!')):
1024 print >>self
.verbose
, '** Skip multiple %s of %s (code %r)' % (cname
, self
.namemappers
[0].findcodename('class', code
)[0], code
)
1025 raise RuntimeError, "About to skip non-empty class"
1030 for prop
in properties
:
1031 [pname
, pcode
, what
] = prop
1033 superclasses
.append(what
)
1036 pname
= identify(pname
)
1039 superclassnames
= []
1040 for superclass
in superclasses
:
1041 superId
, superDesc
, dummy
= superclass
1042 superclassname
, fullyqualifiedname
, module
= self
.findcodename("class", superId
)
1043 # I don't think this is correct:
1044 if superclassname
== cname
:
1045 pass # superclassnames.append(fullyqualifiedname)
1047 superclassnames
.append(superclassname
)
1050 self
.fp
.write("%s._superclassnames = %r\n"%(cname
, superclassnames
))
1052 for elem
in elements
:
1053 [ecode
, keyform
] = elem
1056 name
, ename
, module
= self
.findcodename('class', ecode
)
1059 self
.fp
.write("# XXXX %s element %r not found!!\n"%(cname
, ecode
))
1061 elist
.append((name
, ename
))
1067 self
.fp
.write("%s._privpropdict = {\n"%cname
)
1069 self
.fp
.write(" '%s' : _Prop_%s,\n"%(n
, n
))
1070 self
.fp
.write("}\n")
1071 self
.fp
.write("%s._privelemdict = {\n"%cname
)
1072 for n
, fulln
in elist
:
1073 self
.fp
.write(" '%s' : %s,\n"%(n
, fulln
))
1074 self
.fp
.write("}\n")
1076 def compilecomparison(self
, comp
):
1077 [name
, code
, comment
] = comp
1078 iname
= identify(name
)
1079 self
.namemappers
[0].addnamecode('comparison', iname
, code
)
1081 self
.fp
.write("class %s(aetools.NComparison):\n" % iname
)
1082 self
.fp
.write(' """%s - %s """\n' % (ascii(name
), ascii(comment
)))
1084 def compileenumeration(self
, enum
):
1085 [code
, items
] = enum
1086 name
= "_Enum_%s" % identify(code
)
1088 self
.fp
.write("%s = {\n" % name
)
1090 self
.compileenumerator(item
)
1091 self
.fp
.write("}\n\n")
1092 self
.namemappers
[0].addnamecode('enum', name
, code
)
1095 def compileenumerator(self
, item
):
1096 [name
, code
, desc
] = item
1097 self
.fp
.write(" %r : %r,\t# %s\n" % (identify(name
), code
, ascii(desc
)))
1099 def checkforenum(self
, enum
):
1100 """This enum code is used by an event. Make sure it's available"""
1101 name
, fullname
, module
= self
.findcodename('enum', enum
)
1104 self
.fp
.write("_Enum_%s = None # XXXX enum %s not found!!\n"%(identify(enum
), ascii(enum
)))
1108 self
.fp
.write("from %s import %s\n"%(module
, name
))
1110 def dumpindex(self
):
1113 self
.fp
.write("\n#\n# Indices of types declared in this module\n#\n")
1115 self
.fp
.write("_classdeclarations = {\n")
1116 classlist
= self
.namemappers
[0].getall('class')
1118 for k
, v
in classlist
:
1119 self
.fp
.write(" %r : %s,\n" % (k
, v
))
1120 self
.fp
.write("}\n")
1122 self
.fp
.write("\n_propdeclarations = {\n")
1123 proplist
= self
.namemappers
[0].getall('property')
1125 for k
, v
in proplist
:
1126 self
.fp
.write(" %r : _Prop_%s,\n" % (k
, v
))
1127 self
.fp
.write("}\n")
1129 self
.fp
.write("\n_compdeclarations = {\n")
1130 complist
= self
.namemappers
[0].getall('comparison')
1132 for k
, v
in complist
:
1133 self
.fp
.write(" %r : %s,\n" % (k
, v
))
1134 self
.fp
.write("}\n")
1136 self
.fp
.write("\n_enumdeclarations = {\n")
1137 enumlist
= self
.namemappers
[0].getall('enum')
1139 for k
, v
in enumlist
:
1140 self
.fp
.write(" %r : %s,\n" % (k
, v
))
1141 self
.fp
.write("}\n")
1143 def compiledata(data
):
1144 [type, description
, flags
] = data
1145 return "%r -- %r %s" % (type, description
, compiledataflags(flags
))
1148 return data
[0] == 'null'
1150 def is_optional(data
):
1151 return (data
[2] & 0x8000)
1154 return (data
[2] & 0x2000)
1156 def getdatadoc(data
):
1157 [type, descr
, flags
] = data
1163 return 'an AE object reference'
1164 return "undocumented, typecode %r"%(type,)
1166 dataflagdict
= {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
1167 def compiledataflags(flags
):
1171 if i
in dataflagdict
.keys():
1172 bits
.append(dataflagdict
[i
])
1174 bits
.append(repr(i
))
1175 return '[%s]' % string
.join(bits
)
1178 """Return a string with all non-ascii characters hex-encoded"""
1179 if type(str) != type(''):
1180 return map(ascii
, str)
1183 if c
in ('\t', '\n', '\r') or ' ' <= c
< chr(0x7f):
1186 rv
= rv
+ '\\' + 'x%02.2x' % ord(c
)
1190 """Turn any string into an identifier:
1191 - replace space by _
1192 - replace other illegal chars by _xx_ (hex code)
1193 - append _ if the result is a python keyword
1196 return "empty_ae_name_"
1198 ok
= string
.ascii_letters
+ '_'
1199 ok2
= ok
+ string
.digits
1206 rv
= rv
+ '_%02.2x_'%ord(c
)
1208 if keyword
.iskeyword(rv
):
1212 # Call the main program
1214 if __name__
== '__main__':