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