##// END OF EJS Templates
Workbook.current & match_h...
Ville M. Vainio -
Show More
@@ -1,409 +1,431 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 __str__(self):
100 return "<LeoNode %s>" % str(self.p)
101
102 __repr__ = __str__
103
99 104 def __get_h(self): return self.p.headString()
100 105 def __set_h(self,val):
101 106 print "set head",val
102 107 c.beginUpdate()
103 108 try:
104 109 c.setHeadString(self.p,val)
105 110 finally:
106 111 c.endUpdate()
107 112
108 113 h = property( __get_h, __set_h, doc = "Node headline string")
109 114
110 115 def __get_b(self): return self.p.bodyString()
111 116 def __set_b(self,val):
112 117 print "set body",val
113 118 c.beginUpdate()
114 119 try:
115 120 c.setBodyString(self.p, val)
116 121 finally:
117 122 c.endUpdate()
118 123
119 124 b = property(__get_b, __set_b, doc = "Nody body string")
120 125
121 126 def __set_val(self, val):
122 127 self.b = format_for_leo(val)
123 128
124 129 v = property(lambda self: eval_node(self), __set_val, doc = "Node evaluated value")
125 130
126 131 def __set_l(self,val):
127 132 self.b = '\n'.join(val )
128 133 l = property(lambda self : IPython.genutils.SList(self.b.splitlines()),
129 134 __set_l, doc = "Node value as string list")
130 135
131 136 def __iter__(self):
132 137 """ Iterate through nodes direct children """
133 138
134 139 return (LeoNode(p) for p in self.p.children_iter())
135 140
136 141 def __children(self):
137 142 d = {}
138 143 for child in self:
139 144 head = child.h
140 145 tup = head.split(None,1)
141 146 if len(tup) > 1 and tup[0] == '@k':
142 147 d[tup[1]] = child
143 148 continue
144 149
145 150 if not valid_attribute(head):
146 151 d[head] = child
147 152 continue
148 153 return d
149 154 def keys(self):
150 155 d = self.__children()
151 156 return d.keys()
152 157 def __getitem__(self, key):
153 158 """ wb.foo['Some stuff'] Return a child node with headline 'Some stuff'
154 159
155 160 If key is a valid python name (e.g. 'foo'), look for headline '@k foo' as well
156 161 """
157 162 key = str(key)
158 163 d = self.__children()
159 164 return d[key]
160 165 def __setitem__(self, key, val):
161 166 """ You can do wb.foo['My Stuff'] = 12 to create children
162 167
163 168 This will create 'My Stuff' as a child of foo (if it does not exist), and
164 169 do .v = 12 assignment.
165 170
166 171 Exception:
167 172
168 173 wb.foo['bar'] = 12
169 174
170 175 will create a child with headline '@k bar', because bar is a valid python name
171 176 and we don't want to crowd the WorkBook namespace with (possibly numerous) entries
172 177 """
173 178 key = str(key)
174 179 d = self.__children()
175 180 if key in d:
176 181 d[key].v = val
177 182 return
178 183
179 184 if not valid_attribute(key):
180 185 head = key
181 186 else:
182 187 head = '@k ' + key
183 188 p = c.createLastChildNode(self.p, head, '')
184 189 LeoNode(p).v = val
185 def __delitem__(self,key):
186 pass
190
187 191 def ipush(self):
188 192 """ Does push-to-ipython on the node """
189 193 push_from_leo(self)
194
190 195 def go(self):
191 196 """ Set node as current node (to quickly see it in Outline) """
192 197 c.beginUpdate()
193 198 try:
194 199 c.setCurrentPosition(self.p)
195 200 finally:
196 201 c.endUpdate()
197 202
203 def script(self):
204 """ Method to get the 'tangled' contents of the node
205
206 (parse @others, << section >> references etc.)
207 """
208 return g.getScript(c,self.p,useSelectedText=False,useSentinels=False)
209
198 210 def __get_uA(self):
199 211 p = self.p
200 212 # Create the uA if necessary.
201 213 if not hasattr(p.v.t,'unknownAttributes'):
202 214 p.v.t.unknownAttributes = {}
203 215
204 216 d = p.v.t.unknownAttributes.setdefault('ipython', {})
205 217 return d
218
206 219 uA = property(__get_uA, doc = "Access persistent unknownAttributes of node")
207 220
208 221
209 222 class LeoWorkbook:
210 223 """ class for 'advanced' node access
211 224
212 225 Has attributes for all "discoverable" nodes. Node is discoverable if it
213 226 either
214 227
215 228 - has a valid python name (Foo, bar_12)
216 229 - is a parent of an anchor node (if it has a child '@a foo', it is visible as foo)
217 230
218 231 """
219 232 def __getattr__(self, key):
220 233 if key.startswith('_') or key == 'trait_names' or not valid_attribute(key):
221 234 raise AttributeError
222 235 cells = all_cells()
223 236 p = cells.get(key, None)
224 237 if p is None:
225 238 p = add_var(key)
226 239
227 240 return LeoNode(p)
228 241
229 242 def __str__(self):
230 243 return "<LeoWorkbook>"
231 244 def __setattr__(self,key, val):
232 245 raise AttributeError("Direct assignment to workbook denied, try wb.%s.v = %s" % (key,val))
233 246
234 247 __repr__ = __str__
235 248
236 249 def __iter__(self):
237 250 """ Iterate all (even non-exposed) nodes """
238 251 cells = all_cells()
239 252 return (LeoNode(p) for p in c.allNodes_iter())
240 253
254 current = property(lambda self: LeoNode(c.currentPosition()), doc = "Currently selected node")
255
256 def match_h(self, regex):
257 cmp = re.compile(regex)
258 for node in self:
259 if re.match(cmp, node.h, re.IGNORECASE):
260 yield node
261 return
262
241 263 ip.user_ns['wb'] = LeoWorkbook()
242 264
243 265
244 266
245 267 @IPython.generics.complete_object.when_type(LeoWorkbook)
246 268 def workbook_complete(obj, prev):
247 return all_cells().keys()
269 return all_cells().keys() + [s for s in prev if not s.startswith('_')]
248 270
249 271
250 272 def add_var(varname):
251 273 c.beginUpdate()
252 274 try:
253 275 p2 = g.findNodeAnywhere(c,varname)
254 276 if p2:
255 277 return
256 278
257 279 rootpos = g.findNodeAnywhere(c,'@ipy-results')
258 280 if not rootpos:
259 281 rootpos = c.currentPosition()
260 282 p2 = rootpos.insertAsLastChild()
261 283 c.setHeadString(p2,varname)
262 284 return p2
263 285 finally:
264 286 c.endUpdate()
265 287
266 288 def add_file(self,fname):
267 289 p2 = c.currentPosition().insertAfter()
268 290
269 291 push_from_leo = CommandChainDispatcher()
270 292
271 293 def expose_ileo_push(f, prio = 0):
272 294 push_from_leo.add(f, prio)
273 295
274 296 def push_ipython_script(node):
275 297 """ Execute the node body in IPython, as if it was entered in interactive prompt """
276 298 c.beginUpdate()
277 299 try:
278 300 ohist = ip.IP.output_hist
279 301 hstart = len(ip.IP.input_hist)
280 script = g.getScript(c,node.p,useSelectedText=False,forcePythonSentinels=False,useSentinels=False)
302 script = node.script()
281 303
282 304 script = g.splitLines(script + '\n')
283 305
284 306 ip.runlines(script)
285 307
286 308 has_output = False
287 309 for idx in range(hstart,len(ip.IP.input_hist)):
288 310 val = ohist.get(idx,None)
289 311 if val is None:
290 312 continue
291 313 has_output = True
292 314 inp = ip.IP.input_hist[idx]
293 315 if inp.strip():
294 316 es('In: %s' % (inp[:40], ))
295 317
296 318 es('<%d> %s' % (idx, pprint.pformat(ohist[idx],width = 40)))
297 319
298 320 if not has_output:
299 321 es('ipy run: %s (%d LL)' %( node.h,len(script)))
300 322 finally:
301 323 c.endUpdate()
302 324
303 325 # this should be the LAST one that will be executed, and it will never raise TryNext
304 326 expose_ileo_push(push_ipython_script, 1000)
305 327
306 328 def eval_body(body):
307 329 try:
308 330 val = ip.ev(body)
309 331 except:
310 332 # just use stringlist if it's not completely legal python expression
311 333 val = IPython.genutils.SList(body.splitlines())
312 334 return val
313 335
314 336 def push_plain_python(node):
315 337 if not node.h.endswith('P'):
316 338 raise TryNext
317 script = g.getScript(c,node.p,useSelectedText=False,forcePythonSentinels=False,useSentinels=False)
339 script = node.script()
318 340 lines = script.count('\n')
319 341 try:
320 342 exec script in ip.user_ns
321 343 except:
322 344 print " -- Exception in script:\n"+script + "\n --"
323 345 raise
324 346 es('ipy plain: %s (%d LL)' % (node.h,lines))
325 347
326 348 expose_ileo_push(push_plain_python, 100)
327 349
328 350 def push_cl_node(node):
329 351 """ If node starts with @cl, eval it
330 352
331 353 The result is put to root @ipy-results node
332 354 """
333 355 if not node.b.startswith('@cl'):
334 356 raise TryNext
335 357
336 358 p2 = g.findNodeAnywhere(c,'@ipy-results')
337 359 val = node.v
338 360 if p2:
339 361 es("=> @ipy-results")
340 362 LeoNode(p2).v = val
341 363 es(val)
342 364
343 365 expose_ileo_push(push_cl_node,100)
344 366
345 367 def push_position_from_leo(p):
346 368 push_from_leo(LeoNode(p))
347 369
348 370 ip.user_ns['leox'].push = push_position_from_leo
349 371
350 372 def leo_f(self,s):
351 373 """ open file(s) in Leo
352 374
353 375 Takes an mglob pattern, e.g. '%leo *.cpp' or %leo 'rec:*.cpp'
354 376 """
355 377 import os
356 378 from IPython.external import mglob
357 379
358 380 files = mglob.expand(s)
359 381 c.beginUpdate()
360 382 try:
361 383 for fname in files:
362 384 p = g.findNodeAnywhere(c,'@auto ' + fname)
363 385 if not p:
364 386 p = c.currentPosition().insertAfter()
365 387
366 388 p.setHeadString('@auto ' + fname)
367 389 if os.path.isfile(fname):
368 390 c.setBodyString(p,open(fname).read())
369 391 c.selectPosition(p)
370 392 finally:
371 393 c.endUpdate()
372 394
373 395 ip.expose_magic('leo',leo_f)
374 396
375 397 def leoref_f(self,s):
376 398 """ Quick reference for ILeo """
377 399 import textwrap
378 400 print textwrap.dedent("""\
379 401 %leo file - open file in leo
380 402 wb.foo.v - eval node foo (i.e. headstring is 'foo' or '@ipy foo')
381 403 wb.foo.v = 12 - assign to body of node foo
382 404 wb.foo.b - read or write the body of node foo
383 405 wb.foo.l - body of node foo as string list
384 406
385 407 for el in wb.foo:
386 408 print el.v
387 409
388 410 """
389 411 )
390 412 ip.expose_magic('leoref',leoref_f)
391 413
392 414 def show_welcome():
393 415 print "------------------"
394 416 print "Welcome to Leo-enabled IPython session!"
395 417 print "Try %leoref for quick reference."
396 418 import IPython.platutils
397 419 IPython.platutils.set_term_title('ILeo')
398 420 IPython.platutils.freeze_term_title()
399 421
400 422 def run_leo_startup_node():
401 423 p = g.findNodeAnywhere(c,'@ipy-startup')
402 424 if p:
403 425 print "Running @ipy-startup nodes"
404 426 for n in LeoNode(p):
405 427 push_from_leo(n)
406 428
407 429 run_leo_startup_node()
408 430 show_welcome()
409 431
General Comments 0
You need to be logged in to leave comments. Login now