##// END OF EJS Templates
ipy_leo: LeoNode.uA for unknownAttributes
Ville M. Vainio -
Show More
@@ -1,401 +1,409 b''
1 1 """ ILeo - Leo plugin for IPython
2 2
3 3
4 4 """
5 5 import IPython.ipapi
6 6 import IPython.genutils
7 7 import IPython.generics
8 8 from IPython.hooks import CommandChainDispatcher
9 9 import re
10 10 import UserDict
11 11 from IPython.ipapi import TryNext
12 12
13 13 ip = IPython.ipapi.get()
14 14 leo = ip.user_ns['leox']
15 15 c,g = leo.c, leo.g
16 16
17 17 # will probably be overwritten by user, but handy for experimentation early on
18 18 ip.user_ns['c'] = c
19 19 ip.user_ns['g'] = g
20 20
21 21
22 22 from IPython.external.simplegeneric import generic
23 23 import pprint
24 24
25 25 def es(s):
26 26 g.es(s, tabName = 'IPython')
27 27 pass
28 28
29 29 @generic
30 30 def format_for_leo(obj):
31 31 """ Convert obj to string representiation (for editing in Leo)"""
32 32 return pprint.pformat(obj)
33 33
34 34 @format_for_leo.when_type(list)
35 35 def format_list(obj):
36 36 return "\n".join(str(s) for s in obj)
37 37
38 38 attribute_re = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
39 39 def valid_attribute(s):
40 40 return attribute_re.match(s)
41 41
42 42 def all_cells():
43 43 d = {}
44 44 for p in c.allNodes_iter():
45 45 h = p.headString()
46 46 if h.startswith('@a '):
47 47 d[h.lstrip('@a ').strip()] = p.parent().copy()
48 48 elif not valid_attribute(h):
49 49 continue
50 50 d[h] = p.copy()
51 51 return d
52 52
53 53
54 54
55 55 def eval_node(n):
56 56 body = n.b
57 57 if not body.startswith('@cl'):
58 58 # plain python repr node, just eval it
59 59 return ip.ev(n.b)
60 60 # @cl nodes deserve special treatment - first eval the first line (minus cl), then use it to call the rest of body
61 61 first, rest = body.split('\n',1)
62 62 tup = first.split(None, 1)
63 63 # @cl alone SPECIAL USE-> dump var to user_ns
64 64 if len(tup) == 1:
65 65 val = ip.ev(rest)
66 66 ip.user_ns[n.h] = val
67 67 es("%s = %s" % (n.h, repr(val)[:20] ))
68 68 return val
69 69
70 70 cl, hd = tup
71 71
72 72 xformer = ip.ev(hd.strip())
73 73 es('Transform w/ %s' % repr(xformer))
74 74 return xformer(rest, n)
75 75
76 76 class LeoNode(object, UserDict.DictMixin):
77 77 """ Node in Leo outline
78 78
79 79 Most important attributes (getters/setters available:
80 80 .v - evaluate node, can also be alligned
81 81 .b, .h - body string, headline string
82 82 .l - value as string list
83 83
84 84 Also supports iteration,
85 85
86 86 setitem / getitem (indexing):
87 87 wb.foo['key'] = 12
88 88 assert wb.foo['key'].v == 12
89 89
90 90 Note the asymmetry on setitem and getitem! Also other
91 91 dict methods are available.
92 92
93 93 .ipush() - run push-to-ipython
94 94
95 95 """
96 96 def __init__(self,p):
97 97 self.p = p.copy()
98 98
99 def get_h(self): return self.p.headString()
100 def set_h(self,val):
99 def __get_h(self): return self.p.headString()
100 def __set_h(self,val):
101 101 print "set head",val
102 102 c.beginUpdate()
103 103 try:
104 104 c.setHeadString(self.p,val)
105 105 finally:
106 106 c.endUpdate()
107 107
108 h = property( get_h, set_h, doc = "Node headline string")
108 h = property( __get_h, __set_h, doc = "Node headline string")
109 109
110 def get_b(self): return self.p.bodyString()
111 def set_b(self,val):
110 def __get_b(self): return self.p.bodyString()
111 def __set_b(self,val):
112 112 print "set body",val
113 113 c.beginUpdate()
114 114 try:
115 115 c.setBodyString(self.p, val)
116 116 finally:
117 117 c.endUpdate()
118 118
119 b = property(get_b, set_b, doc = "Nody body string")
119 b = property(__get_b, __set_b, doc = "Nody body string")
120 120
121 def set_val(self, val):
121 def __set_val(self, val):
122 122 self.b = format_for_leo(val)
123 123
124 v = property(lambda self: eval_node(self), set_val, doc = "Node evaluated value")
124 v = property(lambda self: eval_node(self), __set_val, doc = "Node evaluated value")
125 125
126 def set_l(self,val):
126 def __set_l(self,val):
127 127 self.b = '\n'.join(val )
128 128 l = property(lambda self : IPython.genutils.SList(self.b.splitlines()),
129 set_l, doc = "Node value as string list")
129 __set_l, doc = "Node value as string list")
130 130
131 131 def __iter__(self):
132 132 """ Iterate through nodes direct children """
133 133
134 134 return (LeoNode(p) for p in self.p.children_iter())
135 135
136 def _children(self):
136 def __children(self):
137 137 d = {}
138 138 for child in self:
139 139 head = child.h
140 140 tup = head.split(None,1)
141 141 if len(tup) > 1 and tup[0] == '@k':
142 142 d[tup[1]] = child
143 143 continue
144 144
145 145 if not valid_attribute(head):
146 146 d[head] = child
147 147 continue
148 148 return d
149 149 def keys(self):
150 d = self._children()
150 d = self.__children()
151 151 return d.keys()
152 152 def __getitem__(self, key):
153 153 """ wb.foo['Some stuff'] Return a child node with headline 'Some stuff'
154 154
155 155 If key is a valid python name (e.g. 'foo'), look for headline '@k foo' as well
156 156 """
157 157 key = str(key)
158 d = self._children()
158 d = self.__children()
159 159 return d[key]
160 160 def __setitem__(self, key, val):
161 161 """ You can do wb.foo['My Stuff'] = 12 to create children
162 162
163 163 This will create 'My Stuff' as a child of foo (if it does not exist), and
164 164 do .v = 12 assignment.
165 165
166 166 Exception:
167 167
168 168 wb.foo['bar'] = 12
169 169
170 170 will create a child with headline '@k bar', because bar is a valid python name
171 171 and we don't want to crowd the WorkBook namespace with (possibly numerous) entries
172 172 """
173 173 key = str(key)
174 d = self._children()
174 d = self.__children()
175 175 if key in d:
176 176 d[key].v = val
177 177 return
178 178
179 179 if not valid_attribute(key):
180 180 head = key
181 181 else:
182 182 head = '@k ' + key
183 183 p = c.createLastChildNode(self.p, head, '')
184 184 LeoNode(p).v = val
185 185 def __delitem__(self,key):
186 186 pass
187 187 def ipush(self):
188 188 """ Does push-to-ipython on the node """
189 189 push_from_leo(self)
190 190 def go(self):
191 191 """ Set node as current node (to quickly see it in Outline) """
192 192 c.beginUpdate()
193 193 try:
194 194 c.setCurrentPosition(self.p)
195 195 finally:
196 c.endUpdate()
196 c.endUpdate()
197 197
198 def __get_uA(self):
199 p = self.p
200 # Create the uA if necessary.
201 if not hasattr(p.v.t,'unknownAttributes'):
202 p.v.t.unknownAttributes = {}
198 203
204 d = p.v.t.unknownAttributes.setdefault('ipython', {})
205 return d
206 uA = property(__get_uA, doc = "Access persistent unknownAttributes of node'")
199 207
200 208
201 209 class LeoWorkbook:
202 210 """ class for 'advanced' node access
203 211
204 212 Has attributes for all "discoverable" nodes. Node is discoverable if it
205 213 either
206 214
207 215 - has a valid python name (Foo, bar_12)
208 216 - is a parent of an anchor node (if it has a child '@a foo', it is visible as foo)
209 217
210 218 """
211 219 def __getattr__(self, key):
212 220 if key.startswith('_') or key == 'trait_names' or not valid_attribute(key):
213 221 raise AttributeError
214 222 cells = all_cells()
215 223 p = cells.get(key, None)
216 224 if p is None:
217 225 p = add_var(key)
218 226
219 227 return LeoNode(p)
220 228
221 229 def __str__(self):
222 230 return "<LeoWorkbook>"
223 231 def __setattr__(self,key, val):
224 232 raise AttributeError("Direct assignment to workbook denied, try wb.%s.v = %s" % (key,val))
225 233
226 234 __repr__ = __str__
227 235
228 236 def __iter__(self):
229 237 """ Iterate all (even non-exposed) nodes """
230 238 cells = all_cells()
231 239 return (LeoNode(p) for p in c.allNodes_iter())
232 240
233 241 ip.user_ns['wb'] = LeoWorkbook()
234 242
235 243
236 244
237 245 @IPython.generics.complete_object.when_type(LeoWorkbook)
238 246 def workbook_complete(obj, prev):
239 247 return all_cells().keys()
240 248
241 249
242 250 def add_var(varname):
243 251 c.beginUpdate()
244 252 try:
245 253 p2 = g.findNodeAnywhere(c,varname)
246 254 if p2:
247 255 return
248 256
249 257 rootpos = g.findNodeAnywhere(c,'@ipy-results')
250 258 if not rootpos:
251 259 rootpos = c.currentPosition()
252 260 p2 = rootpos.insertAsLastChild()
253 261 c.setHeadString(p2,varname)
254 262 return p2
255 263 finally:
256 264 c.endUpdate()
257 265
258 266 def add_file(self,fname):
259 267 p2 = c.currentPosition().insertAfter()
260 268
261 269 push_from_leo = CommandChainDispatcher()
262 270
263 271 def expose_ileo_push(f, prio = 0):
264 272 push_from_leo.add(f, prio)
265 273
266 274 def push_ipython_script(node):
267 275 """ Execute the node body in IPython, as if it was entered in interactive prompt """
268 276 c.beginUpdate()
269 277 try:
270 278 ohist = ip.IP.output_hist
271 279 hstart = len(ip.IP.input_hist)
272 280 script = g.getScript(c,node.p,useSelectedText=False,forcePythonSentinels=False,useSentinels=False)
273 281
274 282 script = g.splitLines(script + '\n')
275 283
276 284 ip.runlines(script)
277 285
278 286 has_output = False
279 287 for idx in range(hstart,len(ip.IP.input_hist)):
280 288 val = ohist.get(idx,None)
281 289 if val is None:
282 290 continue
283 291 has_output = True
284 292 inp = ip.IP.input_hist[idx]
285 293 if inp.strip():
286 294 es('In: %s' % (inp[:40], ))
287 295
288 296 es('<%d> %s' % (idx, pprint.pformat(ohist[idx],width = 40)))
289 297
290 298 if not has_output:
291 299 es('ipy run: %s (%d LL)' %( node.h,len(script)))
292 300 finally:
293 301 c.endUpdate()
294 302
295 303 # this should be the LAST one that will be executed, and it will never raise TryNext
296 304 expose_ileo_push(push_ipython_script, 1000)
297 305
298 306 def eval_body(body):
299 307 try:
300 308 val = ip.ev(body)
301 309 except:
302 310 # just use stringlist if it's not completely legal python expression
303 311 val = IPython.genutils.SList(body.splitlines())
304 312 return val
305 313
306 314 def push_plain_python(node):
307 315 if not node.h.endswith('P'):
308 316 raise TryNext
309 317 script = g.getScript(c,node.p,useSelectedText=False,forcePythonSentinels=False,useSentinels=False)
310 318 lines = script.count('\n')
311 319 try:
312 320 exec script in ip.user_ns
313 321 except:
314 322 print " -- Exception in script:\n"+script + "\n --"
315 323 raise
316 324 es('ipy plain: %s (%d LL)' % (node.h,lines))
317 325
318 326 expose_ileo_push(push_plain_python, 100)
319 327
320 328 def push_cl_node(node):
321 329 """ If node starts with @cl, eval it
322 330
323 331 The result is put to root @ipy-results node
324 332 """
325 333 if not node.b.startswith('@cl'):
326 334 raise TryNext
327 335
328 336 p2 = g.findNodeAnywhere(c,'@ipy-results')
329 337 val = node.v
330 338 if p2:
331 339 es("=> @ipy-results")
332 340 LeoNode(p2).v = val
333 341 es(val)
334 342
335 343 expose_ileo_push(push_cl_node,100)
336 344
337 345 def push_position_from_leo(p):
338 346 push_from_leo(LeoNode(p))
339 347
340 348 ip.user_ns['leox'].push = push_position_from_leo
341 349
342 350 def leo_f(self,s):
343 351 """ open file(s) in Leo
344 352
345 353 Takes an mglob pattern, e.g. '%leo *.cpp' or %leo 'rec:*.cpp'
346 354 """
347 355 import os
348 356 from IPython.external import mglob
349 357
350 358 files = mglob.expand(s)
351 359 c.beginUpdate()
352 360 try:
353 361 for fname in files:
354 362 p = g.findNodeAnywhere(c,'@auto ' + fname)
355 363 if not p:
356 364 p = c.currentPosition().insertAfter()
357 365
358 366 p.setHeadString('@auto ' + fname)
359 367 if os.path.isfile(fname):
360 368 c.setBodyString(p,open(fname).read())
361 369 c.selectPosition(p)
362 370 finally:
363 371 c.endUpdate()
364 372
365 373 ip.expose_magic('leo',leo_f)
366 374
367 375 def leoref_f(self,s):
368 376 """ Quick reference for ILeo """
369 377 import textwrap
370 378 print textwrap.dedent("""\
371 379 %leo file - open file in leo
372 380 wb.foo.v - eval node foo (i.e. headstring is 'foo' or '@ipy foo')
373 381 wb.foo.v = 12 - assign to body of node foo
374 382 wb.foo.b - read or write the body of node foo
375 383 wb.foo.l - body of node foo as string list
376 384
377 385 for el in wb.foo:
378 386 print el.v
379 387
380 388 """
381 389 )
382 390 ip.expose_magic('leoref',leoref_f)
383 391
384 392 def show_welcome():
385 393 print "------------------"
386 394 print "Welcome to Leo-enabled IPython session!"
387 395 print "Try %leoref for quick reference."
388 396 import IPython.platutils
389 397 IPython.platutils.set_term_title('ILeo')
390 398 IPython.platutils.freeze_term_title()
391 399
392 400 def run_leo_startup_node():
393 401 p = g.findNodeAnywhere(c,'@ipy-startup')
394 402 if p:
395 403 print "Running @ipy-startup nodes"
396 404 for n in LeoNode(p):
397 405 push_from_leo(n)
398 406
399 407 run_leo_startup_node()
400 408 show_welcome()
401 409
General Comments 0
You need to be logged in to leave comments. Login now