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