Move setting of ioready 'wait' earlier in call chain, to
[python/dscho.git] / Mac / Contrib / AECaptureParser / AECaptureParser.py
blob3d74f64e85a9dd16f9ae1587ea9de02cc4ddf6f3
1 '''
2 AECaptureParser makes a brave attempt to convert the text output
3 of the very handy Lasso Capture AE control panel
4 into close-enough executable python code.
6 In a roundabout way AECaptureParser offers the way to write lines of AppleScript
7 and convert them to python code. Once Pythonised, the code can be made prettier,
8 and it can run without Capture or Script Editor being open.
10 You need Lasso Capture AE from Blueworld:
11 ftp://ftp.blueworld.com/Lasso251/LassoCaptureAE.hqx
13 Lasso Capture AE prints structured ascii representations in a small window.
14 As these transcripts can be very complex, cut and paste to AECaptureParser, it parses and writes
15 python code that will, when executed, cause the same events to happen.
16 It's been tested with some household variety events, I'm sure there will be tons that
17 don't work.
19 All objects are converted to standard aetypes.ObjectSpecifier instances.
21 How to use:
22 1. Start the Capture window
23 2. Cause the desired appleevent to happen
24 - by writing a line of applescript in Script Editor and running it (!)
25 - by recording some action in Script Editor and running it
26 3. Find the events in Capture:
27 - make sure you get the appropriate events, cull if necessary
28 - sometimes Capture barfs, just quit and start Capture again, run events again
29 - AECaptureParser can process multiple events - it will just make more code.
30 4. Copy and paste in this script and execute
31 5. It will print python code that, when executed recreates the events.
33 Example:
34 For instance the following line of AppleScript in Script Editor
35 tell application "Finder"
36 return application processes
37 end tell
38 will result in the following transcript:
39 [event: target="Finder", class=core, id=getd]
40 '----':obj {form:indx, want:type(pcap), seld:abso(«616C6C20»), from:'null'()}
41 [/event]
42 Feed a string with this (and perhaps more) events to AECaptureParser
44 Some mysteries:
45 * what is '&subj' - it is sent in an activate event: &subj:'null'()
46 The activate event works when this is left out. A possibility?
47 * needs to deal with embedded aliasses
50 '''
51 __version__ = '0.002'
52 __author__ = 'evb'
55 import string
57 opentag = '{'
58 closetag = '}'
62 import aetools
63 import aetypes
65 class eventtalker(aetools.TalkTo):
66 pass
68 def processes():
69 '''Helper function to get the list of current processes and their creators
70 This code was mostly written by AECaptureParser! It ain't pretty, but that's not python's fault!'''
71 talker = eventtalker('MACS')
72 _arguments = {}
73 _attributes = {}
74 p = []
75 names = []
76 creators = []
77 results = []
78 # first get the list of process names
79 _arguments['----'] = aetypes.ObjectSpecifier(want=aetypes.Type('pcap'),
80 form="indx", seld=aetypes.Unknown('abso', "all "), fr=None)
81 _reply, _arguments, _attributes = talker.send('core', 'getd', _arguments, _attributes)
82 if _arguments.has_key('errn'):
83 raise aetools.Error, aetools.decodeerror(_arguments)
84 if _arguments.has_key('----'):
85 p = _arguments['----']
86 for proc in p:
87 names.append(proc.seld)
88 # then get the list of process creators
89 _arguments = {}
90 _attributes = {}
91 AEobject_00 = aetypes.ObjectSpecifier(want=aetypes.Type('pcap'), form="indx", seld=aetypes.Unknown('abso', "all "), fr=None)
92 AEobject_01 = aetypes.ObjectSpecifier(want=aetypes.Type('prop'), form="prop", seld=aetypes.Type('fcrt'), fr=AEobject_00)
93 _arguments['----'] = AEobject_01
94 _reply, _arguments, _attributes = talker.send('core', 'getd', _arguments, _attributes)
95 if _arguments.has_key('errn'):
96 raise aetools.Error, aetools.decodeerror(_arguments)
97 if _arguments.has_key('----'):
98 p = _arguments['----']
99 for proc in p:
100 creators.append(proc.type)
101 # then put the lists together
102 for i in range(len(names)):
103 results.append((names[i], creators[i]))
104 return results
107 class AECaptureParser:
108 '''convert a captured appleevent-description into executable python code'''
109 def __init__(self, aetext):
110 self.aetext = aetext
111 self.events = []
112 self.arguments = {}
113 self.objectindex = 0
114 self.varindex = 0
115 self.currentevent = {'variables':{}, 'arguments':{}, 'objects':{}}
116 self.parse()
118 def parse(self):
119 self.lines = string.split(self.aetext, '\n')
120 for l in self.lines:
121 if l[:7] == '[event:':
122 self.eventheader(l)
123 elif l[:7] == '[/event':
124 if len(self.currentevent)<>0:
125 self.events.append(self.currentevent)
126 self.currentevent = {'variables':{}, 'arguments':{}, 'objects':{}}
127 self.objectindex = 0
128 else:
129 self.line(l)
131 def line(self, value):
132 '''interpret literals, variables, lists etc.'''
133 # stuff in [ ], l ists
134 varstart = string.find(value, '[')
135 varstop = string.find(value, ']')
136 if varstart <> -1 and varstop <> -1 and varstop>varstart:
137 variable = value[varstart:varstop+1]
138 name = 'aevar_'+string.zfill(self.varindex, 2)
139 self.currentevent['variables'][name] = variable
140 value = value[:varstart]+name+value[varstop+1:]
141 self.varindex = self.varindex + 1
142 # stuff in « »
143 # these are 'ordinal' descriptors of 4 letter codes, so translate
144 varstart = string.find(value, '«')
145 varstop = string.find(value, '»')
146 if varstart <> -1 and varstop <> -1 and varstop>varstart:
147 variable = value[varstart+1:varstop]
148 t = ''
149 for i in range(0, len(variable), 2):
150 c = eval('0x'+variable[i : i+2])
151 t = t + chr(c)
153 name = 'aevar_'+string.zfill(self.varindex, 2)
154 self.currentevent['variables'][name] = '"' + t + '"'
155 value = value[:varstart]+name+value[varstop+1:]
156 self.varindex = self.varindex + 1
157 pos = string.find(value, ':')
158 if pos==-1:return
159 ok = 1
160 while ok <> None:
161 value, ok = self.parseobject(value)
162 self.currentevent['arguments'].update(self.splitparts(value, ':'))
164 # remove the &subj argument?
165 if self.currentevent['arguments'].has_key('&subj'):
166 del self.currentevent['arguments']['&subj']
168 # check for arguments len(a) < 4, and pad with spaces
169 for k in self.currentevent['arguments'].keys():
170 if len(k)<4:
171 newk = k + (4-len(k))*' '
172 self.currentevent['arguments'][newk] = self.currentevent['arguments'][k]
173 del self.currentevent['arguments'][k]
175 def parseobject(self, obj):
176 a, b = self.findtag(obj)
177 stuff = None
178 if a<>None and b<>None:
179 stuff = obj[a:b]
180 name = 'AEobject_'+string.zfill(self.objectindex, 2)
181 self.currentevent['objects'][name] = self.splitparts(stuff, ':')
182 obj = obj[:a-5] + name + obj[b+1:]
183 self.objectindex = self.objectindex +1
184 return obj, stuff
186 def nextopen(self, pos, text):
187 return string.find(text, opentag, pos)
189 def nextclosed(self, pos, text):
190 return string.find(text, closetag, pos)
192 def nexttag(self, pos, text):
193 start = self.nextopen(pos, text)
194 stop = self.nextclosed(pos, text)
195 if start == -1:
196 if stop == -1:
197 return -1, -1
198 return 0, stop
199 if start < stop and start<>-1:
200 return 1, start
201 else:
202 return 0, stop
204 def findtag(self, text):
205 p = -1
206 last = None,None
207 while 1:
208 kind, p = self.nexttag(p+1, text)
209 if last[0]==1 and kind==0:
210 return last[1]+len(opentag), p
211 if (kind, p) == (-1, -1):
212 break
213 last=kind, p
214 return None, None
216 def splitparts(self, txt, splitter):
217 res = {}
218 parts = string.split(txt, ', ')
219 for p in parts:
220 pos = string.find(p, splitter)
221 key = string.strip(p[:pos])
222 value = string.strip(p[pos+1:])
223 res[key] = self.map(value)
224 return res
226 def eventheader(self, hdr):
227 self.currentevent['event'] = self.splitparts(hdr[7:-1], '=')
229 def printobject(self, d):
230 '''print one object as python code'''
231 t = []
232 obj = {}
233 obj.update(d)
234 t.append("aetypes.ObjectSpecifier(")
235 if obj.has_key('want'):
236 t.append('want=' + self.map(obj['want']))
237 del obj['want']
238 t.append(', ')
239 if obj.has_key('form'):
240 t.append('form=' + addquotes(self.map(obj['form'])))
241 del obj['form']
242 t.append(', ')
243 if obj.has_key('seld'):
244 t.append('seld=' + self.map(obj['seld']))
245 del obj['seld']
246 t.append(', ')
247 if obj.has_key('from'):
248 t.append('fr=' + self.map(obj['from']))
249 del obj['from']
250 if len(obj.keys()) > 0:
251 print '# ', `obj`
252 t.append(")")
253 return string.join(t, '')
255 def map(self, t):
256 '''map some Capture syntax to python
257 matchstring : [(old, new), ... ]
259 m = {
260 'type(': [('type(', "aetypes.Type('"), (')', "')")],
261 "'null'()": [("'null'()", "None")],
262 'abso(': [('abso(', "aetypes.Unknown('abso', ")],
263 '–': [('–', '"')],
264 '”': [('”', '"')],
265 '[': [('[', '('), (', ', ',')],
266 ']': [(']', ')')],
267 '«': [('«', "«")],
268 '»': [('»', "»")],
271 for k in m.keys():
272 if string.find(t, k) <> -1:
273 for old, new in m[k]:
274 p = string.split(t, old)
275 t = string.join(p, new)
276 return t
278 def printevent(self, i):
279 '''print the entire captured sequence as python'''
280 evt = self.events[i]
281 code = []
282 code.append('\n# start event ' + `i` + ', talking to ' + evt['event']['target'])
283 # get the signature for the target application
284 code.append('talker = eventtalker("'+self.gettarget(evt['event']['target'])+'")')
285 code.append("_arguments = {}")
286 code.append("_attributes = {}")
287 # write the variables
288 for key, value in evt['variables'].items():
289 value = evt['variables'][key]
290 code.append(key + ' = ' + value)
291 # write the object in the right order
292 objkeys = evt['objects'].keys()
293 objkeys.sort()
294 for key in objkeys:
295 value = evt['objects'][key]
296 code.append(key + ' = ' + self.printobject(value))
297 # then write the arguments
298 for key, value in evt['arguments'].items():
299 code.append("_arguments[" + addquotes(key) + "] = " + value )
300 code.append('_reply, _arguments, _attributes = talker.send("'+
301 evt['event']['class']+'", "'+evt['event']['id']+'", _arguments, _attributes)')
302 code.append("if _arguments.has_key('errn'):")
303 code.append('\traise aetools.Error, aetools.decodeerror(_arguments)')
304 code.append("if _arguments.has_key('----'):")
305 code.append("\tprint _arguments['----']")
306 code.append('# end event ' + `i`)
307 return string.join(code, '\n')
309 def gettarget(self, target):
310 '''get the signature for the target application'''
311 target = target[1:-1]
312 if target == 'Finder':
313 return "MACS"
314 apps = processes()
315 for name, creator in apps:
316 if name == target:
317 return creator
318 return '****'
320 def makecode(self):
321 code = []
322 code.append("\n\n")
323 code.append("# code generated by AECaptureParser v " + __version__)
324 code.append("# imports, definitions for all events")
325 code.append("import aetools")
326 code.append("import aetypes")
327 code.append("class eventtalker(aetools.TalkTo):")
328 code.append("\tpass")
329 code.append("# the events")
330 # print the events
331 for i in range(len(self.events)):
332 code.append(self.printevent(i))
333 code.append("# end code")
334 return string.join(code, '\n')
336 def addquotes(txt):
337 quotes = ['"', "'"]
338 if not txt[0] in quotes and not txt[-1] in quotes:
339 return '"'+txt+'"'
340 return txt
347 # ------------------------------------------
348 # the factory
349 # ------------------------------------------
351 # for instance, this event was captured from the Script Editor asking the Finder for a list of active processes.
353 eventreceptacle = """
355 [event: target="Finder", class=core, id=setd]
356 '----':obj {form:prop, want:type(prop), seld:type(posn), from:obj {form:name, want:type(cfol), seld:–MoPar:Data:DevDev:Python:Python 1.5.2c1:Extensions”, from:'null'()}}, data:[100, 10]
357 [/event]
361 aet = AECaptureParser(eventreceptacle)
362 print aet.makecode()