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