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