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.
19 import distutils
.sysconfig
21 from Carbon
.Res
import *
27 _MAC_LIB_FOLDER
=os
.path
.dirname(aetools
.__file
__)
28 DEFAULT_STANDARD_PACKAGEFOLDER
=os
.path
.join(_MAC_LIB_FOLDER
, 'lib-scriptpackages')
29 DEFAULT_USER_PACKAGEFOLDER
=distutils
.sysconfig
.get_python_lib()
32 sys
.stderr
.write("Usage: %s [opts] application-or-resource-file\n" % sys
.argv
[0])
33 sys
.stderr
.write("""Options:
34 --output pkgdir Pathname of the output package (short: -o)
35 --resource Parse resource file in stead of launching application (-r)
36 --base package Use another base package in stead of default StdSuites (-b)
37 --edit old=new Edit suite names, use empty new to skip a suite (-e)
38 --creator code Set creator code for package (-c)
39 --dump Dump aete resource to stdout in stead of creating module (-d)
40 --verbose Tell us what happens (-v)
46 SHORTOPTS
= "rb:o:e:c:dv"
47 LONGOPTS
= ("resource", "base=", "output=", "edit=", "creator=", "dump", "verbose")
49 opts
, args
= getopt
.getopt(sys
.argv
[1:], SHORTOPTS
, LONGOPTS
)
50 except getopt
.GetoptError
:
53 process_func
= processfile
54 basepkgname
= 'StdSuites'
57 creatorsignature
= None
62 if o
in ('-r', '--resource'):
63 process_func
= processfile_fromresource
64 if o
in ('-b', '--base'):
66 if o
in ('-o', '--output'):
68 if o
in ('-e', '--edit'):
72 edit_modnames
.append(split
)
73 if o
in ('-c', '--creator'):
75 sys
.stderr
.write("creator must be 4-char string\n")
78 if o
in ('-d', '--dump'):
80 if o
in ('-v', '--verbose'):
84 if output
and len(args
) > 1:
85 sys
.stderr
.write("%s: cannot specify --output with multiple inputs\n" % sys
.argv
[0])
89 process_func(filename
, output
=output
, basepkgname
=basepkgname
,
90 edit_modnames
=edit_modnames
, creatorsignature
=creatorsignature
,
91 dump
=dump
, verbose
=verbose
)
95 def main_interactive(interact
=0, basepkgname
='StdSuites'):
97 # Ask for save-filename for each module
100 # Use default filenames for each module
102 appsfolder
= Carbon
.Folder
.FSFindFolder(-32765, 'apps', 0)
103 filename
= EasyDialogs
.AskFileForOpen(
104 message
='Select scriptable application',
105 dialogOptionFlags
=0x1056, # allow selection of .app bundles
106 defaultLocation
=appsfolder
)
109 if not is_scriptable(filename
):
110 if EasyDialogs
.AskYesNoCancel(
111 "Warning: application does not seem scriptable",
112 yes
="Continue", default
=2, no
="") <= 0:
115 processfile(filename
, edit_modnames
=edit_modnames
, basepkgname
=basepkgname
,
117 except MacOS
.Error
, arg
:
118 print "Error getting terminology:", arg
119 print "Retry, manually parsing resources"
120 processfile_fromresource(filename
, edit_modnames
=edit_modnames
,
121 basepkgname
=basepkgname
, verbose
=sys
.stderr
)
123 def is_scriptable(application
):
124 """Return true if the application is scriptable"""
125 if os
.path
.isdir(application
):
126 plistfile
= os
.path
.join(application
, 'Contents', 'Info.plist')
127 if not os
.path
.exists(plistfile
):
129 plist
= plistlib
.Plist
.fromFile(plistfile
)
130 return plist
.get('NSAppleScriptEnabled', False)
131 # If it is a file test for an aete/aeut resource.
134 refno
= macresource
.open_pathname(application
)
138 n_terminology
= Count1Resources('aete') + Count1Resources('aeut') + \
139 Count1Resources('scsz') + Count1Resources('osiz')
142 return n_terminology
> 0
144 def processfile_fromresource(fullname
, output
=None, basepkgname
=None,
145 edit_modnames
=None, creatorsignature
=None, dump
=None, verbose
=None):
146 """Process all resources in a single file"""
147 if not is_scriptable(fullname
) and verbose
:
148 print >>verbose
, "Warning: app does not seem scriptable: %s" % fullname
151 print >>verbose
, "Processing", fullname
152 rf
= macresource
.open_pathname(fullname
)
156 for i
in range(Count1Resources('aete')):
157 res
= Get1IndResource('aete', 1+i
)
158 resources
.append(res
)
159 for i
in range(Count1Resources('aeut')):
160 res
= Get1IndResource('aeut', 1+i
)
161 resources
.append(res
)
163 print >>verbose
, "\nLISTING aete+aeut RESOURCES IN", repr(fullname
)
165 for res
in resources
:
167 print >>verbose
, "decoding", res
.GetResInfo(), "..."
169 aete
= decode(data
, verbose
)
170 aetelist
.append((aete
, res
.GetResInfo()))
175 # switch back (needed for dialogs in Python)
178 dumpaetelist(aetelist
, dump
)
179 compileaetelist(aetelist
, fullname
, output
=output
,
180 basepkgname
=basepkgname
, edit_modnames
=edit_modnames
,
181 creatorsignature
=creatorsignature
, verbose
=verbose
)
183 def processfile(fullname
, output
=None, basepkgname
=None,
184 edit_modnames
=None, creatorsignature
=None, dump
=None,
186 """Ask an application for its terminology and process that"""
187 if not is_scriptable(fullname
) and verbose
:
188 print >>verbose
, "Warning: app does not seem scriptable: %s" % fullname
190 print >>verbose
, "\nASKING FOR aete DICTIONARY IN", repr(fullname
)
192 aedescobj
, launched
= OSATerminology
.GetAppTerminology(fullname
)
193 except MacOS
.Error
, arg
:
194 if arg
[0] in (-1701, -192): # errAEDescNotFound, resNotFound
196 print >>verbose
, "GetAppTerminology failed with errAEDescNotFound/resNotFound, trying manually"
197 aedata
, sig
= getappterminology(fullname
, verbose
=verbose
)
198 if not creatorsignature
:
199 creatorsignature
= sig
205 print >>verbose
, "Launched", fullname
206 raw
= aetools
.unpack(aedescobj
)
209 print >>verbose
, 'Unpack returned empty value:', raw
213 print >>verbose
, 'Unpack returned value without data:', raw
216 aete
= decode(aedata
.data
, verbose
)
218 dumpaetelist([aete
], dump
)
220 compileaete(aete
, None, fullname
, output
=output
, basepkgname
=basepkgname
,
221 creatorsignature
=creatorsignature
, edit_modnames
=edit_modnames
,
224 def getappterminology(fullname
, verbose
=None):
225 """Get application terminology by sending an AppleEvent"""
226 # First check that we actually can send AppleEvents
227 if not MacOS
.WMAvailable():
228 raise RuntimeError, "Cannot send AppleEvents, no access to window manager"
229 # Next, a workaround for a bug in MacOS 10.2: sending events will hang unless
230 # you have created an event loop first.
232 Carbon
.Evt
.WaitNextEvent(0,0)
233 if os
.path
.isdir(fullname
):
234 # Now get the signature of the application, hoping it is a bundle
235 pkginfo
= os
.path
.join(fullname
, 'Contents', 'PkgInfo')
236 if not os
.path
.exists(pkginfo
):
237 raise RuntimeError, "No PkgInfo file found"
238 tp_cr
= open(pkginfo
, 'rb').read()
241 # Assume it is a file
242 cr
, tp
= MacOS
.GetCreatorAndType(fullname
)
243 # Let's talk to it and ask for its AETE
244 talker
= aetools
.TalkTo(cr
)
247 except (MacOS
.Error
, aetools
.Error
), arg
:
249 print >>verbose
, 'Warning: start() failed, continuing anyway:', arg
250 reply
= talker
.send("ascr", "gdte")
251 #reply2 = talker.send("ascr", "gdut")
252 # Now pick the bits out of the return that we need.
253 return reply
[1]['----'], cr
256 def compileaetelist(aetelist
, fullname
, output
=None, basepkgname
=None,
257 edit_modnames
=None, creatorsignature
=None, verbose
=None):
258 for aete
, resinfo
in aetelist
:
259 compileaete(aete
, resinfo
, fullname
, output
=output
,
260 basepkgname
=basepkgname
, edit_modnames
=edit_modnames
,
261 creatorsignature
=creatorsignature
, verbose
=verbose
)
263 def dumpaetelist(aetelist
, output
):
265 pprint
.pprint(aetelist
, output
)
267 def decode(data
, verbose
=None):
268 """Decode a resource into a python data structure"""
269 f
= StringIO
.StringIO(data
)
270 aete
= generic(getaete
, f
)
271 aete
= simplify(aete
)
273 unprocessed
= len(f
.read())
275 if unprocessed
and verbose
:
276 verbose
.write("%d processed + %d unprocessed = %d total\n" %
277 (processed
, unprocessed
, total
))
281 """Recursively replace singleton tuples by their constituent item"""
282 if type(item
) is types
.ListType
:
283 return map(simplify
, item
)
284 elif type(item
) == types
.TupleType
and len(item
) == 2:
285 return simplify(item
[1])
290 # Here follows the aete resource decoder.
291 # It is presented bottom-up instead of top-down because there are direct
292 # references to the lower-level part-decoders from the high-level part-decoders.
294 def getbyte(f
, *args
):
297 raise EOFError, 'in getbyte' + str(args
)
300 def getword(f
, *args
):
304 raise EOFError, 'in getword' + str(args
)
305 return (ord(s
[0])<<8) |
ord(s
[1])
307 def getlong(f
, *args
):
311 raise EOFError, 'in getlong' + str(args
)
312 return (ord(s
[0])<<24) |
(ord(s
[1])<<16) |
(ord(s
[2])<<8) |
ord(s
[3])
314 def getostype(f
, *args
):
318 raise EOFError, 'in getostype' + str(args
)
321 def getpstr(f
, *args
):
324 raise EOFError, 'in getpstr[1]' + str(args
)
326 if nbytes
== 0: return ''
329 raise EOFError, 'in getpstr[2]' + str(args
)
336 ## print align:', repr(c)
338 def getlist(f
, description
, getitem
):
341 for i
in range(count
):
342 list.append(generic(getitem
, f
))
346 def alt_generic(what
, f
, *args
):
347 print "generic", repr(what
), args
348 res
= vageneric(what
, f
, args
)
349 print '->', repr(res
)
352 def generic(what
, f
, *args
):
353 if type(what
) == types
.FunctionType
:
354 return apply(what
, (f
,) + args
)
355 if type(what
) == types
.ListType
:
358 item
= apply(generic
, thing
[:1] + (f
,) + thing
[1:])
359 record
.append((thing
[1], item
))
361 return "BAD GENERIC ARGS: %r" % (what
,)
365 (getpstr
, "description"),
370 (getostype
, "keyword"),
375 (getpstr
, "description"),
376 (getostype
, "suite code"),
377 (getostype
, "event code"),
378 (getdata
, "returns"),
379 (getdata
, "accepts"),
380 (getlist
, "optional arguments", getargument
)
389 (getlist
, "keyform", getostype
)
393 (getostype
, "class code"),
394 (getpstr
, "description"),
395 (getlist
, "properties", getproperty
),
396 (getlist
, "elements", getelement
)
399 (getpstr
, "operator name"),
400 (getostype
, "operator ID"),
401 (getpstr
, "operator comment"),
404 (getpstr
, "enumerator name"),
405 (getostype
, "enumerator ID"),
406 (getpstr
, "enumerator comment")
409 (getostype
, "enumeration ID"),
410 (getlist
, "enumerator", getenumerator
)
413 (getpstr
, "suite name"),
414 (getpstr
, "suite description"),
415 (getostype
, "suite ID"),
416 (getword
, "suite level"),
417 (getword
, "suite version"),
418 (getlist
, "events", getevent
),
419 (getlist
, "classes", getclass
),
420 (getlist
, "comparisons", getcomparison
),
421 (getlist
, "enumerations", getenumeration
)
424 (getword
, "major/minor version in BCD"),
425 (getword
, "language code"),
426 (getword
, "script code"),
427 (getlist
, "suites", getsuite
)
430 def compileaete(aete
, resinfo
, fname
, output
=None, basepkgname
=None,
431 edit_modnames
=None, creatorsignature
=None, verbose
=None):
432 """Generate code for a full aete resource. fname passed for doc purposes"""
433 [version
, language
, script
, suites
] = aete
434 major
, minor
= divmod(version
, 256)
435 if not creatorsignature
:
436 creatorsignature
, dummy
= MacOS
.GetCreatorAndType(fname
)
437 packagename
= identify(os
.path
.splitext(os
.path
.basename(fname
))[0])
439 packagename
= packagename
+'_lang%d'%language
441 packagename
= packagename
+'_script%d'%script
442 if len(packagename
) > 27:
443 packagename
= packagename
[:27]
445 # XXXX Put this in site-packages if it isn't a full pathname?
446 if not os
.path
.exists(output
):
450 pathname
= EasyDialogs
.AskFolder(message
='Create and select package folder for %s'%packagename
,
451 defaultLocation
=DEFAULT_USER_PACKAGEFOLDER
)
455 packagename
= os
.path
.split(os
.path
.normpath(pathname
))[1]
457 basepkgname
= EasyDialogs
.AskFolder(message
='Package folder for base suite (usually StdSuites)',
458 defaultLocation
=DEFAULT_STANDARD_PACKAGEFOLDER
)
460 dirname
, basepkgname
= os
.path
.split(os
.path
.normpath(basepkgname
))
461 if dirname
and not dirname
in sys
.path
:
462 sys
.path
.insert(0, dirname
)
463 basepackage
= __import__(basepkgname
)
470 compiler
= SuiteCompiler(suite
, basepackage
, output
, edit_modnames
, verbose
)
471 code
, modname
, precompinfo
= compiler
.precompilesuite()
474 allprecompinfo
= allprecompinfo
+ precompinfo
475 suiteinfo
= suite
, pathname
, modname
476 suitelist
.append((code
, modname
))
477 allsuites
.append(compiler
)
478 for compiler
in allsuites
:
479 compiler
.compilesuite(major
, minor
, language
, script
, fname
, allprecompinfo
)
480 initfilename
= os
.path
.join(output
, '__init__.py')
481 fp
= open(initfilename
, 'w')
482 MacOS
.SetCreatorAndType(initfilename
, 'Pyth', 'TEXT')
484 fp
.write("Package generated from %s\n"%ascii(fname
))
486 fp
.write("Resource %s resid %d %s\n"%(ascii(resinfo
[1]), resinfo
[0], ascii(resinfo
[2])))
488 fp
.write('import aetools\n')
489 fp
.write('Error = aetools.Error\n')
491 for code
, modname
in suitelist
:
492 fp
.write("import %s\n" % modname
)
493 fp
.write("\n\n_code_to_module = {\n")
494 for code
, modname
in suitelist
:
495 fp
.write(" '%s' : %s,\n"%(ascii(code
), modname
))
497 fp
.write("\n\n_code_to_fullname = {\n")
498 for code
, modname
in suitelist
:
499 fp
.write(" '%s' : ('%s.%s', '%s'),\n"%(ascii(code
), packagename
, modname
, modname
))
501 for code
, modname
in suitelist
:
502 fp
.write("from %s import *\n"%modname
)
504 # Generate property dicts and element dicts for all types declared in this module
505 fp
.write("\ndef getbaseclasses(v):\n")
506 fp
.write(" if not getattr(v, '_propdict', None):\n")
507 fp
.write(" v._propdict = {}\n")
508 fp
.write(" v._elemdict = {}\n")
509 fp
.write(" for superclassname in getattr(v, '_superclassnames', []):\n")
510 fp
.write(" superclass = eval(superclassname)\n")
511 fp
.write(" getbaseclasses(superclass)\n")
512 fp
.write(" v._propdict.update(getattr(superclass, '_propdict', {}))\n")
513 fp
.write(" v._elemdict.update(getattr(superclass, '_elemdict', {}))\n")
514 fp
.write(" v._propdict.update(getattr(v, '_privpropdict', {}))\n")
515 fp
.write(" v._elemdict.update(getattr(v, '_privelemdict', {}))\n")
517 fp
.write("import StdSuites\n")
518 allprecompinfo
.sort()
520 fp
.write("\n#\n# Set property and element dictionaries now that all classes have been defined\n#\n")
521 for codenamemapper
in allprecompinfo
:
522 for k
, v
in codenamemapper
.getall('class'):
523 fp
.write("getbaseclasses(%s)\n" % v
)
525 # Generate a code-to-name mapper for all of the types (classes) declared in this module
526 application_class
= None
528 fp
.write("\n#\n# Indices of types declared in this module\n#\n")
529 fp
.write("_classdeclarations = {\n")
530 for codenamemapper
in allprecompinfo
:
531 for k
, v
in codenamemapper
.getall('class'):
532 fp
.write(" %r : %s,\n" % (k
, v
))
534 application_class
= v
539 fp
.write("\n\nclass %s(%s_Events"%(packagename
, suitelist
[0][1]))
540 for code
, modname
in suitelist
[1:]:
541 fp
.write(",\n %s_Events"%modname
)
542 fp
.write(",\n aetools.TalkTo):\n")
543 fp
.write(" _signature = %r\n\n"%(creatorsignature
,))
544 fp
.write(" _moduleName = '%s'\n\n"%packagename
)
545 if application_class
:
546 fp
.write(" _elemdict = %s._elemdict\n" % application_class
)
547 fp
.write(" _propdict = %s._propdict\n" % application_class
)
551 def __init__(self
, suite
, basepackage
, output
, edit_modnames
, verbose
):
553 self
.basepackage
= basepackage
554 self
.edit_modnames
= edit_modnames
556 self
.verbose
= verbose
558 # Set by precompilesuite
562 # Set by compilesuite
564 self
.basemodule
= None
565 self
.enumsneeded
= {}
567 def precompilesuite(self
):
568 """Parse a single suite without generating the output. This step is needed
569 so we can resolve recursive references by suites to enums/comps/etc declared
571 [name
, desc
, code
, level
, version
, events
, classes
, comps
, enums
] = self
.suite
573 modname
= identify(name
)
574 if len(modname
) > 28:
575 modname
= modname
[:27]
576 if self
.edit_modnames
is None:
577 self
.pathname
= EasyDialogs
.AskFileForSave(message
='Python output file',
578 savedFileName
=modname
+'.py')
580 for old
, new
in self
.edit_modnames
:
584 self
.pathname
= os
.path
.join(self
.output
, modname
+ '.py')
587 if not self
.pathname
:
588 return None, None, None
590 self
.modname
= os
.path
.splitext(os
.path
.split(self
.pathname
)[1])[0]
592 if self
.basepackage
and self
.basepackage
._code
_to
_module
.has_key(code
):
593 # We are an extension of a baseclass (usually an application extending
594 # Standard_Suite or so). Import everything from our base module
595 basemodule
= self
.basepackage
._code
_to
_module
[code
]
597 # We are not an extension.
600 self
.enumsneeded
= {}
602 self
.findenumsinevent(event
)
604 objc
= ObjectCompiler(None, self
.modname
, basemodule
, interact
=(self
.edit_modnames
is None),
605 verbose
=self
.verbose
)
607 objc
.compileclass(cls
)
609 objc
.fillclasspropsandelems(cls
)
611 objc
.compilecomparison(comp
)
613 objc
.compileenumeration(enum
)
615 for enum
in self
.enumsneeded
.keys():
616 objc
.checkforenum(enum
)
620 precompinfo
= objc
.getprecompinfo(self
.modname
)
622 return code
, self
.modname
, precompinfo
624 def compilesuite(self
, major
, minor
, language
, script
, fname
, precompinfo
):
625 """Generate code for a single suite"""
626 [name
, desc
, code
, level
, version
, events
, classes
, comps
, enums
] = self
.suite
627 # Sort various lists, so re-generated source is easier compared
628 def class_sorter(k1
, k2
):
629 """Sort classes by code, and make sure main class sorts before synonyms"""
630 # [name, code, desc, properties, elements] = cls
631 if k1
[1] < k2
[1]: return -1
632 if k1
[1] > k2
[1]: return 1
633 if not k2
[3] or k2
[3][0][1] == 'c@#!':
634 # This is a synonym, the other one is better
636 if not k1
[3] or k1
[3][0][1] == 'c@#!':
637 # This is a synonym, the other one is better
642 classes
.sort(class_sorter
)
646 self
.fp
= fp
= open(self
.pathname
, 'w')
647 MacOS
.SetCreatorAndType(self
.pathname
, 'Pyth', 'TEXT')
649 fp
.write('"""Suite %s: %s\n' % (ascii(name
), ascii(desc
)))
650 fp
.write("Level %d, version %d\n\n" % (level
, version
))
651 fp
.write("Generated from %s\n"%ascii(fname
))
652 fp
.write("AETE/AEUT resource version %d/%d, language %d, script %d\n" % \
653 (major
, minor
, language
, script
))
656 fp
.write('import aetools\n')
657 fp
.write('import MacOS\n\n')
658 fp
.write("_code = %r\n\n"% (code
,))
659 if self
.basepackage
and self
.basepackage
._code
_to
_module
.has_key(code
):
660 # We are an extension of a baseclass (usually an application extending
661 # Standard_Suite or so). Import everything from our base module
662 fp
.write('from %s import *\n'%self
.basepackage
._code
_to
_fullname
[code
][0])
663 basemodule
= self
.basepackage
._code
_to
_module
[code
]
664 elif self
.basepackage
and self
.basepackage
._code
_to
_module
.has_key(code
.lower()):
665 # This is needed by CodeWarrior and some others.
666 fp
.write('from %s import *\n'%self
.basepackage
._code
_to
_fullname
[code
.lower()][0])
667 basemodule
= self
.basepackage
._code
_to
_module
[code
.lower()]
669 # We are not an extension.
671 self
.basemodule
= basemodule
672 self
.compileclassheader()
674 self
.enumsneeded
= {}
677 self
.compileevent(event
)
679 fp
.write(" pass\n\n")
681 objc
= ObjectCompiler(fp
, self
.modname
, basemodule
, precompinfo
, interact
=(self
.edit_modnames
is None),
682 verbose
=self
.verbose
)
684 objc
.compileclass(cls
)
686 objc
.fillclasspropsandelems(cls
)
688 objc
.compilecomparison(comp
)
690 objc
.compileenumeration(enum
)
692 for enum
in self
.enumsneeded
.keys():
693 objc
.checkforenum(enum
)
697 def compileclassheader(self
):
698 """Generate class boilerplate"""
699 classname
= '%s_Events'%self
.modname
701 modshortname
= string
.split(self
.basemodule
.__name
__, '.')[-1]
702 baseclassname
= '%s_Events'%modshortname
703 self
.fp
.write("class %s(%s):\n\n"%(classname
, baseclassname
))
705 self
.fp
.write("class %s:\n\n"%classname
)
707 def compileevent(self
, event
):
708 """Generate code for a single event"""
709 [name
, desc
, code
, subcode
, returns
, accepts
, arguments
] = event
711 funcname
= identify(name
)
713 # generate name->keyword map
716 fp
.write(" _argmap_%s = {\n"%funcname
)
718 fp
.write(" %r : %r,\n"%(identify(a
[0]), a
[1]))
722 # Generate function header
724 has_arg
= (not is_null(accepts
))
725 opt_arg
= (has_arg
and is_optional(accepts
))
727 fp
.write(" def %s(self, "%funcname
)
730 fp
.write("_object, ") # Include direct object, if it has one
732 fp
.write("_object=None, ") # Also include if it is optional
734 fp
.write("_no_object=None, ") # For argument checking
735 fp
.write("_attributes={}, **_arguments):\n") # include attribute dict and args
737 # Generate doc string (important, since it may be the only
738 # available documentation, due to our name-remaping)
740 fp
.write(' """%s: %s\n'%(ascii(name
), ascii(desc
)))
742 fp
.write(" Required argument: %s\n"%getdatadoc
(accepts
))
744 fp
.write(" Optional argument: %s\n"%getdatadoc
(accepts
))
745 for arg
in arguments
:
746 fp
.write(" Keyword argument %s: %s\n"%(identify(arg
[0]),
748 fp
.write(" Keyword argument _attributes: AppleEvent attribute dictionary\n")
749 if not is_null(returns
):
750 fp
.write(" Returns: %s\n"%getdatadoc
(returns
))
753 # Fiddle the args so everything ends up in 'arguments' dictionary
755 fp
.write(" _code = %r\n"% (code
,))
756 fp
.write(" _subcode = %r\n\n"% (subcode
,))
758 # Do keyword name substitution
761 fp
.write(" aetools.keysubst(_arguments, self._argmap_%s)\n"%funcname
)
763 fp
.write(" if _arguments: raise TypeError, 'No optional args expected'\n")
765 # Stuff required arg (if there is one) into arguments
768 fp
.write(" _arguments['----'] = _object\n")
770 fp
.write(" if _object:\n")
771 fp
.write(" _arguments['----'] = _object\n")
773 fp
.write(" if _no_object != None: raise TypeError, 'No direct arg expected'\n")
776 # Do enum-name substitution
783 fp
.write(" aetools.enumsubst(_arguments, %r, _Enum_%s)\n" %
784 (kname
, identify(ename
)))
785 self
.enumsneeded
[ename
] = 1
790 fp
.write(" _reply, _arguments, _attributes = self.send(_code, _subcode,\n")
791 fp
.write(" _arguments, _attributes)\n")
795 fp
.write(" if _arguments.get('errn', 0):\n")
796 fp
.write(" raise aetools.Error, aetools.decodeerror(_arguments)\n")
797 fp
.write(" # XXXX Optionally decode result\n")
801 fp
.write(" if _arguments.has_key('----'):\n")
803 fp
.write(" # XXXX Should do enum remapping here...\n")
804 fp
.write(" return _arguments['----']\n")
807 def findenumsinevent(self
, event
):
808 """Find all enums for a single event"""
809 [name
, desc
, code
, subcode
, returns
, accepts
, arguments
] = event
814 self
.enumsneeded
[ename
] = 1
817 # This class stores the code<->name translations for a single module. It is used
818 # to keep the information while we're compiling the module, but we also keep these objects
819 # around so if one suite refers to, say, an enum in another suite we know where to
820 # find it. Finally, if we really can't find a code, the user can add modules by
823 class CodeNameMapper
:
825 def __init__(self
, interact
=1, verbose
=None):
838 self
.modulename
= None
839 self
.star_imported
= 0
840 self
.can_interact
= interact
841 self
.verbose
= verbose
843 def addnamecode(self
, type, name
, code
):
844 self
.name2code
[type][name
] = code
845 if not self
.code2name
[type].has_key(code
):
846 self
.code2name
[type][code
] = name
848 def hasname(self
, name
):
849 for dict in self
.name2code
.values():
850 if dict.has_key(name
):
854 def hascode(self
, type, code
):
855 return self
.code2name
[type].has_key(code
)
857 def findcodename(self
, type, code
):
858 if not self
.hascode(type, code
):
859 return None, None, None
860 name
= self
.code2name
[type][code
]
861 if self
.modulename
and not self
.star_imported
:
862 qualname
= '%s.%s'%(self
.modulename
, name
)
865 return name
, qualname
, self
.modulename
867 def getall(self
, type):
868 return self
.code2name
[type].items()
870 def addmodule(self
, module
, name
, star_imported
):
871 self
.modulename
= name
872 self
.star_imported
= star_imported
873 for code
, name
in module
._propdeclarations
.items():
874 self
.addnamecode('property', name
, code
)
875 for code
, name
in module
._classdeclarations
.items():
876 self
.addnamecode('class', name
, code
)
877 for code
in module
._enumdeclarations
.keys():
878 self
.addnamecode('enum', '_Enum_'+identify(code
), code
)
879 for code
, name
in module
._compdeclarations
.items():
880 self
.addnamecode('comparison', name
, code
)
882 def prepareforexport(self
, name
=None):
883 if not self
.modulename
:
884 self
.modulename
= name
887 class ObjectCompiler
:
888 def __init__(self
, fp
, modname
, basesuite
, othernamemappers
=None, interact
=1,
891 self
.verbose
= verbose
892 self
.basesuite
= basesuite
893 self
.can_interact
= interact
894 self
.modulename
= modname
895 self
.namemappers
= [CodeNameMapper(self
.can_interact
, self
.verbose
)]
897 self
.othernamemappers
= othernamemappers
[:]
899 self
.othernamemappers
= []
901 basemapper
= CodeNameMapper(self
.can_interact
, self
.verbose
)
902 basemapper
.addmodule(basesuite
, '', 1)
903 self
.namemappers
.append(basemapper
)
905 def getprecompinfo(self
, modname
):
907 for mapper
in self
.namemappers
:
908 emapper
= mapper
.prepareforexport(modname
)
913 def findcodename(self
, type, code
):
915 # First try: check whether we already know about this code.
916 for mapper
in self
.namemappers
:
917 if mapper
.hascode(type, code
):
918 return mapper
.findcodename(type, code
)
919 # Second try: maybe one of the other modules knows about it.
920 for mapper
in self
.othernamemappers
:
921 if mapper
.hascode(type, code
):
922 self
.othernamemappers
.remove(mapper
)
923 self
.namemappers
.append(mapper
)
925 self
.fp
.write("import %s\n"%mapper
.modulename
)
928 # If all this has failed we ask the user for a guess on where it could
931 m
= self
.askdefinitionmodule(type, code
)
934 if not m
: return None, None, None
935 mapper
= CodeNameMapper(self
.can_interact
, self
.verbose
)
936 mapper
.addmodule(m
, m
.__name
__, 0)
937 self
.namemappers
.append(mapper
)
939 def hasname(self
, name
):
940 for mapper
in self
.othernamemappers
:
941 if mapper
.hasname(name
) and mapper
.modulename
!= self
.modulename
:
943 print >>self
.verbose
, "Duplicate Python identifier:", name
, self
.modulename
, mapper
.modulename
947 def askdefinitionmodule(self
, type, code
):
948 if not self
.can_interact
:
950 print >>self
.verbose
, "** No definition for %s '%s' found" % (type, code
)
952 path
= EasyDialogs
.AskFileForSave(message
='Where is %s %s declared?'%(type, code
))
954 path
, file = os
.path
.split(path
)
955 modname
= os
.path
.splitext(file)[0]
956 if not path
in sys
.path
:
957 sys
.path
.insert(0, path
)
958 m
= __import__(modname
)
959 self
.fp
.write("import %s\n"%modname
)
962 def compileclass(self
, cls
):
963 [name
, code
, desc
, properties
, elements
] = cls
964 pname
= identify(name
)
965 if self
.namemappers
[0].hascode('class', code
):
966 # plural forms and such
967 othername
, dummy
, dummy
= self
.namemappers
[0].findcodename('class', code
)
969 self
.fp
.write("\n%s = %s\n"%(pname
, othername
))
972 self
.fp
.write('\nclass %s(aetools.ComponentItem):\n' % pname
)
973 self
.fp
.write(' """%s - %s """\n' % (ascii(name
), ascii(desc
)))
974 self
.fp
.write(' want = %r\n' % (code
,))
975 self
.namemappers
[0].addnamecode('class', pname
, code
)
976 is_application_class
= (code
== 'capp')
978 for prop
in properties
:
979 self
.compileproperty(prop
, is_application_class
)
981 for elem
in elements
:
982 self
.compileelement(elem
)
984 def compileproperty(self
, prop
, is_application_class
=False):
985 [name
, code
, what
] = prop
987 # Something silly with plurals. Skip it.
989 pname
= identify(name
)
990 if self
.namemappers
[0].hascode('property', code
):
991 # plural forms and such
992 othername
, dummy
, dummy
= self
.namemappers
[0].findcodename('property', code
)
993 if pname
== othername
:
996 self
.fp
.write("\n_Prop_%s = _Prop_%s\n"%(pname
, othername
))
999 self
.fp
.write("class _Prop_%s(aetools.NProperty):\n" % pname
)
1000 self
.fp
.write(' """%s - %s """\n' % (ascii(name
), ascii(what
[1])))
1001 self
.fp
.write(" which = %r\n" % (code
,))
1002 self
.fp
.write(" want = %r\n" % (what
[0],))
1003 self
.namemappers
[0].addnamecode('property', pname
, code
)
1004 if is_application_class
and self
.fp
:
1005 self
.fp
.write("%s = _Prop_%s()\n" % (pname
, pname
))
1007 def compileelement(self
, elem
):
1008 [code
, keyform
] = elem
1010 self
.fp
.write("# element %r as %s\n" % (code
, keyform
))
1012 def fillclasspropsandelems(self
, cls
):
1013 [name
, code
, desc
, properties
, elements
] = cls
1014 cname
= identify(name
)
1015 if self
.namemappers
[0].hascode('class', code
) and \
1016 self
.namemappers
[0].findcodename('class', code
)[0] != cname
:
1017 # This is an other name (plural or so) for something else. Skip.
1018 if self
.fp
and (elements
or len(properties
) > 1 or (len(properties
) == 1 and
1019 properties
[0][1] != 'c@#!')):
1021 print >>self
.verbose
, '** Skip multiple %s of %s (code %r)' % (cname
, self
.namemappers
[0].findcodename('class', code
)[0], code
)
1022 raise RuntimeError, "About to skip non-empty class"
1027 for prop
in properties
:
1028 [pname
, pcode
, what
] = prop
1030 superclasses
.append(what
)
1033 pname
= identify(pname
)
1036 superclassnames
= []
1037 for superclass
in superclasses
:
1038 superId
, superDesc
, dummy
= superclass
1039 superclassname
, fullyqualifiedname
, module
= self
.findcodename("class", superId
)
1040 # I don't think this is correct:
1041 if superclassname
== cname
:
1042 pass # superclassnames.append(fullyqualifiedname)
1044 superclassnames
.append(superclassname
)
1047 self
.fp
.write("%s._superclassnames = %r\n"%(cname
, superclassnames
))
1049 for elem
in elements
:
1050 [ecode
, keyform
] = elem
1053 name
, ename
, module
= self
.findcodename('class', ecode
)
1056 self
.fp
.write("# XXXX %s element %r not found!!\n"%(cname
, ecode
))
1058 elist
.append((name
, ename
))
1064 self
.fp
.write("%s._privpropdict = {\n"%cname
)
1066 self
.fp
.write(" '%s' : _Prop_%s,\n"%(n
, n
))
1067 self
.fp
.write("}\n")
1068 self
.fp
.write("%s._privelemdict = {\n"%cname
)
1069 for n
, fulln
in elist
:
1070 self
.fp
.write(" '%s' : %s,\n"%(n
, fulln
))
1071 self
.fp
.write("}\n")
1073 def compilecomparison(self
, comp
):
1074 [name
, code
, comment
] = comp
1075 iname
= identify(name
)
1076 self
.namemappers
[0].addnamecode('comparison', iname
, code
)
1078 self
.fp
.write("class %s(aetools.NComparison):\n" % iname
)
1079 self
.fp
.write(' """%s - %s """\n' % (ascii(name
), ascii(comment
)))
1081 def compileenumeration(self
, enum
):
1082 [code
, items
] = enum
1083 name
= "_Enum_%s" % identify(code
)
1085 self
.fp
.write("%s = {\n" % name
)
1087 self
.compileenumerator(item
)
1088 self
.fp
.write("}\n\n")
1089 self
.namemappers
[0].addnamecode('enum', name
, code
)
1092 def compileenumerator(self
, item
):
1093 [name
, code
, desc
] = item
1094 self
.fp
.write(" %r : %r,\t# %s\n" % (identify(name
), code
, ascii(desc
)))
1096 def checkforenum(self
, enum
):
1097 """This enum code is used by an event. Make sure it's available"""
1098 name
, fullname
, module
= self
.findcodename('enum', enum
)
1101 self
.fp
.write("_Enum_%s = None # XXXX enum %s not found!!\n"%(identify(enum
), ascii(enum
)))
1105 self
.fp
.write("from %s import %s\n"%(module
, name
))
1107 def dumpindex(self
):
1110 self
.fp
.write("\n#\n# Indices of types declared in this module\n#\n")
1112 self
.fp
.write("_classdeclarations = {\n")
1113 classlist
= self
.namemappers
[0].getall('class')
1115 for k
, v
in classlist
:
1116 self
.fp
.write(" %r : %s,\n" % (k
, v
))
1117 self
.fp
.write("}\n")
1119 self
.fp
.write("\n_propdeclarations = {\n")
1120 proplist
= self
.namemappers
[0].getall('property')
1122 for k
, v
in proplist
:
1123 self
.fp
.write(" %r : _Prop_%s,\n" % (k
, v
))
1124 self
.fp
.write("}\n")
1126 self
.fp
.write("\n_compdeclarations = {\n")
1127 complist
= self
.namemappers
[0].getall('comparison')
1129 for k
, v
in complist
:
1130 self
.fp
.write(" %r : %s,\n" % (k
, v
))
1131 self
.fp
.write("}\n")
1133 self
.fp
.write("\n_enumdeclarations = {\n")
1134 enumlist
= self
.namemappers
[0].getall('enum')
1136 for k
, v
in enumlist
:
1137 self
.fp
.write(" %r : %s,\n" % (k
, v
))
1138 self
.fp
.write("}\n")
1140 def compiledata(data
):
1141 [type, description
, flags
] = data
1142 return "%r -- %r %s" % (type, description
, compiledataflags(flags
))
1145 return data
[0] == 'null'
1147 def is_optional(data
):
1148 return (data
[2] & 0x8000)
1151 return (data
[2] & 0x2000)
1153 def getdatadoc(data
):
1154 [type, descr
, flags
] = data
1160 return 'an AE object reference'
1161 return "undocumented, typecode %r"%(type,)
1163 dataflagdict
= {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
1164 def compiledataflags(flags
):
1168 if i
in dataflagdict
.keys():
1169 bits
.append(dataflagdict
[i
])
1171 bits
.append(repr(i
))
1172 return '[%s]' % string
.join(bits
)
1175 """Return a string with all non-ascii characters hex-encoded"""
1176 if type(str) != type(''):
1177 return map(ascii
, str)
1180 if c
in ('\t', '\n', '\r') or ' ' <= c
< chr(0x7f):
1183 rv
= rv
+ '\\' + 'x%02.2x' % ord(c
)
1187 """Turn any string into an identifier:
1188 - replace space by _
1189 - replace other illegal chars by _xx_ (hex code)
1190 - append _ if the result is a python keyword
1193 return "empty_ae_name_"
1195 ok
= string
.ascii_letters
+ '_'
1196 ok2
= ok
+ string
.digits
1203 rv
= rv
+ '_%02.2x_'%ord(c
)
1205 if keyword
.iskeyword(rv
):
1209 # Call the main program
1211 if __name__
== '__main__':