##// END OF EJS Templates
ipy_leo: LeoNode.go(), __iter__ for WorkBook
Ville M. Vainio -
Show More
@@ -1,380 +1,401 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 99 def get_h(self): return self.p.headString()
100 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 108 h = property( get_h, set_h, doc = "Node headline string")
109 109
110 110 def get_b(self): return self.p.bodyString()
111 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 119 b = property(get_b, set_b, doc = "Nody body string")
120 120
121 121 def set_val(self, val):
122 122 self.b = format_for_leo(val)
123 123
124 124 v = property(lambda self: eval_node(self), set_val, doc = "Node evaluated value")
125 125
126 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 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 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 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 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 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 def go(self):
191 """ Set node as current node (to quickly see it in Outline) """
192 c.beginUpdate()
193 try:
194 c.setCurrentPosition(self.p)
195 finally:
196 c.endUpdate()
190 197
191 198
192 199
193 200
194 201 class LeoWorkbook:
195 """ class for 'advanced' node access """
202 """ class for 'advanced' node access
203
204 Has attributes for all "discoverable" nodes. Node is discoverable if it
205 either
206
207 - has a valid python name (Foo, bar_12)
208 - is a parent of an anchor node (if it has a child '@a foo', it is visible as foo)
209
210 """
196 211 def __getattr__(self, key):
197 212 if key.startswith('_') or key == 'trait_names' or not valid_attribute(key):
198 213 raise AttributeError
199 214 cells = all_cells()
200 215 p = cells.get(key, None)
201 216 if p is None:
202 217 p = add_var(key)
203 218
204 219 return LeoNode(p)
205 220
206 221 def __str__(self):
207 222 return "<LeoWorkbook>"
208 223 def __setattr__(self,key, val):
209 224 raise AttributeError("Direct assignment to workbook denied, try wb.%s.v = %s" % (key,val))
210 225
211 226 __repr__ = __str__
227
228 def __iter__(self):
229 """ Iterate all (even non-exposed) nodes """
230 cells = all_cells()
231 return (LeoNode(p) for p in c.allNodes_iter())
232
212 233 ip.user_ns['wb'] = LeoWorkbook()
213 234
214 235
215 236
216 237 @IPython.generics.complete_object.when_type(LeoWorkbook)
217 238 def workbook_complete(obj, prev):
218 239 return all_cells().keys()
219 240
220 241
221 242 def add_var(varname):
222 243 c.beginUpdate()
223 244 try:
224 245 p2 = g.findNodeAnywhere(c,varname)
225 246 if p2:
226 247 return
227 248
228 249 rootpos = g.findNodeAnywhere(c,'@ipy-results')
229 250 if not rootpos:
230 251 rootpos = c.currentPosition()
231 252 p2 = rootpos.insertAsLastChild()
232 253 c.setHeadString(p2,varname)
233 254 return p2
234 255 finally:
235 256 c.endUpdate()
236 257
237 258 def add_file(self,fname):
238 259 p2 = c.currentPosition().insertAfter()
239 260
240 261 push_from_leo = CommandChainDispatcher()
241 262
242 263 def expose_ileo_push(f, prio = 0):
243 264 push_from_leo.add(f, prio)
244 265
245 266 def push_ipython_script(node):
246 267 """ Execute the node body in IPython, as if it was entered in interactive prompt """
247 268 c.beginUpdate()
248 269 try:
249 270 ohist = ip.IP.output_hist
250 271 hstart = len(ip.IP.input_hist)
251 272 script = g.getScript(c,node.p,useSelectedText=False,forcePythonSentinels=False,useSentinels=False)
252 273
253 274 script = g.splitLines(script + '\n')
254 275
255 276 ip.runlines(script)
256 277
257 278 has_output = False
258 279 for idx in range(hstart,len(ip.IP.input_hist)):
259 280 val = ohist.get(idx,None)
260 281 if val is None:
261 282 continue
262 283 has_output = True
263 284 inp = ip.IP.input_hist[idx]
264 285 if inp.strip():
265 286 es('In: %s' % (inp[:40], ))
266 287
267 288 es('<%d> %s' % (idx, pprint.pformat(ohist[idx],width = 40)))
268 289
269 290 if not has_output:
270 291 es('ipy run: %s (%d LL)' %( node.h,len(script)))
271 292 finally:
272 293 c.endUpdate()
273 294
274 295 # this should be the LAST one that will be executed, and it will never raise TryNext
275 296 expose_ileo_push(push_ipython_script, 1000)
276 297
277 298 def eval_body(body):
278 299 try:
279 300 val = ip.ev(body)
280 301 except:
281 302 # just use stringlist if it's not completely legal python expression
282 303 val = IPython.genutils.SList(body.splitlines())
283 304 return val
284 305
285 306 def push_plain_python(node):
286 307 if not node.h.endswith('P'):
287 308 raise TryNext
288 309 script = g.getScript(c,node.p,useSelectedText=False,forcePythonSentinels=False,useSentinels=False)
289 310 lines = script.count('\n')
290 311 try:
291 312 exec script in ip.user_ns
292 313 except:
293 314 print " -- Exception in script:\n"+script + "\n --"
294 315 raise
295 316 es('ipy plain: %s (%d LL)' % (node.h,lines))
296 317
297 318 expose_ileo_push(push_plain_python, 100)
298 319
299 320 def push_cl_node(node):
300 321 """ If node starts with @cl, eval it
301 322
302 323 The result is put to root @ipy-results node
303 324 """
304 325 if not node.b.startswith('@cl'):
305 326 raise TryNext
306 327
307 328 p2 = g.findNodeAnywhere(c,'@ipy-results')
308 329 val = node.v
309 330 if p2:
310 331 es("=> @ipy-results")
311 332 LeoNode(p2).v = val
312 333 es(val)
313 334
314 335 expose_ileo_push(push_cl_node,100)
315 336
316 337 def push_position_from_leo(p):
317 338 push_from_leo(LeoNode(p))
318 339
319 340 ip.user_ns['leox'].push = push_position_from_leo
320 341
321 342 def leo_f(self,s):
322 343 """ open file(s) in Leo
323 344
324 345 Takes an mglob pattern, e.g. '%leo *.cpp' or %leo 'rec:*.cpp'
325 346 """
326 347 import os
327 348 from IPython.external import mglob
328 349
329 350 files = mglob.expand(s)
330 351 c.beginUpdate()
331 352 try:
332 353 for fname in files:
333 354 p = g.findNodeAnywhere(c,'@auto ' + fname)
334 355 if not p:
335 356 p = c.currentPosition().insertAfter()
336 357
337 358 p.setHeadString('@auto ' + fname)
338 359 if os.path.isfile(fname):
339 360 c.setBodyString(p,open(fname).read())
340 361 c.selectPosition(p)
341 362 finally:
342 363 c.endUpdate()
343 364
344 365 ip.expose_magic('leo',leo_f)
345 366
346 367 def leoref_f(self,s):
347 368 """ Quick reference for ILeo """
348 369 import textwrap
349 370 print textwrap.dedent("""\
350 371 %leo file - open file in leo
351 372 wb.foo.v - eval node foo (i.e. headstring is 'foo' or '@ipy foo')
352 373 wb.foo.v = 12 - assign to body of node foo
353 374 wb.foo.b - read or write the body of node foo
354 375 wb.foo.l - body of node foo as string list
355 376
356 377 for el in wb.foo:
357 378 print el.v
358 379
359 380 """
360 381 )
361 382 ip.expose_magic('leoref',leoref_f)
362 383
363 384 def show_welcome():
364 385 print "------------------"
365 386 print "Welcome to Leo-enabled IPython session!"
366 387 print "Try %leoref for quick reference."
367 388 import IPython.platutils
368 389 IPython.platutils.set_term_title('ILeo')
369 390 IPython.platutils.freeze_term_title()
370 391
371 392 def run_leo_startup_node():
372 393 p = g.findNodeAnywhere(c,'@ipy-startup')
373 394 if p:
374 395 print "Running @ipy-startup nodes"
375 396 for n in LeoNode(p):
376 397 push_from_leo(n)
377 398
378 399 run_leo_startup_node()
379 400 show_welcome()
380 401
General Comments 0
You need to be logged in to leave comments. Login now