##// END OF EJS Templates
ready for wrapped_execute
Barry Wark -
Show More
@@ -1,505 +1,479 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3 3
4 4 """PyObjC classes to provide a Cocoa frontend to the
5 5 IPython.kernel.engineservice.IEngineBase.
6 6
7 7 To add an IPython interpreter to a cocoa app, instantiate an
8 8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 9 NSTextView instance in your UI. That's it.
10 10
11 11 Author: Barry Wark
12 12 """
13 13
14 14 __docformat__ = "restructuredtext en"
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Copyright (C) 2008 The IPython Development Team
18 18 #
19 19 # Distributed under the terms of the BSD License. The full license is in
20 20 # the file COPYING, distributed as part of this software.
21 21 #-----------------------------------------------------------------------------
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Imports
25 25 #-----------------------------------------------------------------------------
26 26
27 27 import sys
28 28 import objc
29 29 import uuid
30 30
31 31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 32 NSLog, NSNotificationCenter, NSMakeRange,\
33 33 NSLocalizedString, NSIntersectionRange,\
34 34 NSString, NSAutoreleasePool
35 35
36 36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 37 NSTextView, NSRulerView, NSVerticalRuler
38 38
39 39 from pprint import saferepr
40 40
41 41 import IPython
42 42 from IPython.kernel.engineservice import ThreadedEngineService
43 43 from IPython.frontend.frontendbase import AsyncFrontEndBase
44 44
45 45 from twisted.internet.threads import blockingCallFromThread
46 46 from twisted.python.failure import Failure
47 47
48 #------------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 49 # Classes to implement the Cocoa frontend
50 #------------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 51
52 52 # TODO:
53 53 # 1. use MultiEngineClient and out-of-process engine rather than
54 54 # ThreadedEngineService?
55 55 # 2. integrate Xgrid launching of engines
56 56
57 57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 58 """Wrap all blocks in an NSAutoreleasePool"""
59 59
60 60 def wrapped_execute(self, msg, lines):
61 61 """wrapped_execute"""
62 62 try:
63 63 p = NSAutoreleasePool.alloc().init()
64 result = self.shell.execute(lines)
65 except Exception,e:
66 # This gives the following:
67 # et=exception class
68 # ev=exception class instance
69 # tb=traceback object
70 et,ev,tb = sys.exc_info()
71 # This call adds attributes to the exception value
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
73 # Add another attribute
74
75 # Create a new exception with the new attributes
76 e = et(ev._ipython_traceback_text)
77 e._ipython_engine_info = msg
78
79 # Re-raise
80 raise e
64 result = super(AutoreleasePoolWrappedThreadedEngineService,
65 self).wrapped_execute(msg, lines)
81 66 finally:
82 67 p.drain()
83 68
84 69 return result
85 70
86 def execute(self, lines):
87 # Only import this if we are going to use this class
88 from twisted.internet import threads
89
90 msg = {'engineid':self.id,
91 'method':'execute',
92 'args':[lines]}
93
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
95 d.addCallback(self.addIDToResult)
96 return d
97 71
98 72
99 73 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
100 74 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
101 75 waitingForEngine = objc.ivar().bool()
102 76 textView = objc.IBOutlet()
103 77
104 78 def init(self):
105 79 self = super(IPythonCocoaController, self).init()
106 80 AsyncFrontEndBase.__init__(self,
107 81 engine=AutoreleasePoolWrappedThreadedEngineService())
108 82 if(self != None):
109 83 self._common_init()
110 84
111 85 return self
112 86
113 87 def _common_init(self):
114 88 """_common_init"""
115 89
116 90 self.userNS = NSMutableDictionary.dictionary()
117 91 self.waitingForEngine = False
118 92
119 93 self.lines = {}
120 94 self.tabSpaces = 4
121 95 self.tabUsesSpaces = True
122 96 self.currentBlockID = self.next_block_ID()
123 97 self.blockRanges = {} # blockID=>NSRange
124 98
125 99
126 100 def awakeFromNib(self):
127 101 """awakeFromNib"""
128 102
129 103 self._common_init()
130 104
131 105 # Start the IPython engine
132 106 self.engine.startService()
133 107 NSLog('IPython engine started')
134 108
135 109 # Register for app termination
136 110 nc = NSNotificationCenter.defaultCenter()
137 111 nc.addObserver_selector_name_object_(
138 112 self,
139 113 'appWillTerminate:',
140 114 NSApplicationWillTerminateNotification,
141 115 None)
142 116
143 117 self.textView.setDelegate_(self)
144 118 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
145 119 r = NSRulerView.alloc().initWithScrollView_orientation_(
146 120 self.textView.enclosingScrollView(),
147 121 NSVerticalRuler)
148 122 self.verticalRulerView = r
149 123 self.verticalRulerView.setClientView_(self.textView)
150 124 self._start_cli_banner()
151 125
152 126
153 127 def appWillTerminate_(self, notification):
154 128 """appWillTerminate"""
155 129
156 130 self.engine.stopService()
157 131
158 132
159 133 def complete(self, token):
160 134 """Complete token in engine's user_ns
161 135
162 136 Parameters
163 137 ----------
164 138 token : string
165 139
166 140 Result
167 141 ------
168 142 Deferred result of
169 143 IPython.kernel.engineservice.IEngineBase.complete
170 144 """
171 145
172 146 return self.engine.complete(token)
173 147
174 148
175 149 def execute(self, block, blockID=None):
176 150 self.waitingForEngine = True
177 151 self.willChangeValueForKey_('commandHistory')
178 152 d = super(IPythonCocoaController, self).execute(block,
179 153 blockID)
180 154 d.addBoth(self._engine_done)
181 155 d.addCallback(self._update_user_ns)
182 156
183 157 return d
184 158
185 159
186 160 def push_(self, namespace):
187 161 """Push dictionary of key=>values to python namespace"""
188 162
189 163 self.waitingForEngine = True
190 164 self.willChangeValueForKey_('commandHistory')
191 165 d = self.engine.push(namespace)
192 166 d.addBoth(self._engine_done)
193 167 d.addCallback(self._update_user_ns)
194 168
195 169
196 170 def pull_(self, keys):
197 171 """Pull keys from python namespace"""
198 172
199 173 self.waitingForEngine = True
200 174 result = blockingCallFromThread(self.engine.pull, keys)
201 175 self.waitingForEngine = False
202 176
203 177 @objc.signature('v@:@I')
204 178 def executeFileAtPath_encoding_(self, path, encoding):
205 179 """Execute file at path in an empty namespace. Update the engine
206 180 user_ns with the resulting locals."""
207 181
208 182 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
209 183 path,
210 184 encoding,
211 185 None)
212 186 self.engine.execute(lines)
213 187
214 188
215 189 def _engine_done(self, x):
216 190 self.waitingForEngine = False
217 191 self.didChangeValueForKey_('commandHistory')
218 192 return x
219 193
220 194 def _update_user_ns(self, result):
221 195 """Update self.userNS from self.engine's namespace"""
222 196 d = self.engine.keys()
223 197 d.addCallback(self._get_engine_namespace_values_for_keys)
224 198
225 199 return result
226 200
227 201
228 202 def _get_engine_namespace_values_for_keys(self, keys):
229 203 d = self.engine.pull(keys)
230 204 d.addCallback(self._store_engine_namespace_values, keys=keys)
231 205
232 206
233 207 def _store_engine_namespace_values(self, values, keys=[]):
234 208 assert(len(values) == len(keys))
235 209 self.willChangeValueForKey_('userNS')
236 210 for (k,v) in zip(keys,values):
237 211 self.userNS[k] = saferepr(v)
238 212 self.didChangeValueForKey_('userNS')
239 213
240 214
241 215 def update_cell_prompt(self, result, blockID=None):
242 216 if(isinstance(result, Failure)):
243 217 self.insert_text(self.input_prompt(),
244 218 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
245 219 scrollToVisible=False
246 220 )
247 221 else:
248 222 self.insert_text(self.input_prompt(number=result['number']),
249 223 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
250 224 scrollToVisible=False
251 225 )
252 226
253 227 return result
254 228
255 229
256 230 def render_result(self, result):
257 231 blockID = result['blockID']
258 232 inputRange = self.blockRanges[blockID]
259 233 del self.blockRanges[blockID]
260 234
261 235 #print inputRange,self.current_block_range()
262 236 self.insert_text('\n' +
263 237 self.output_prompt(number=result['number']) +
264 238 result.get('display',{}).get('pprint','') +
265 239 '\n\n',
266 240 textRange=NSMakeRange(inputRange.location+inputRange.length,
267 241 0))
268 242 return result
269 243
270 244
271 245 def render_error(self, failure):
272 246 self.insert_text('\n' +
273 247 self.output_prompt() +
274 248 '\n' +
275 249 failure.getErrorMessage() +
276 250 '\n\n')
277 251 self.start_new_block()
278 252 return failure
279 253
280 254
281 255 def _start_cli_banner(self):
282 256 """Print banner"""
283 257
284 258 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
285 259 IPython.__version__
286 260
287 261 self.insert_text(banner + '\n\n')
288 262
289 263
290 264 def start_new_block(self):
291 265 """"""
292 266
293 267 self.currentBlockID = self.next_block_ID()
294 268
295 269
296 270
297 271 def next_block_ID(self):
298 272
299 273 return uuid.uuid4()
300 274
301 275 def current_block_range(self):
302 276 return self.blockRanges.get(self.currentBlockID,
303 277 NSMakeRange(self.textView.textStorage().length(),
304 278 0))
305 279
306 280 def current_block(self):
307 281 """The current block's text"""
308 282
309 283 return self.text_for_range(self.current_block_range())
310 284
311 285 def text_for_range(self, textRange):
312 286 """text_for_range"""
313 287
314 288 ts = self.textView.textStorage()
315 289 return ts.string().substringWithRange_(textRange)
316 290
317 291 def current_line(self):
318 292 block = self.text_for_range(self.current_block_range())
319 293 block = block.split('\n')
320 294 return block[-1]
321 295
322 296
323 297 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
324 298 """Insert text into textView at textRange, updating blockRanges
325 299 as necessary
326 300 """
327 301
328 302 if(textRange == None):
329 303 #range for end of text
330 304 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
331 305
332 306 for r in self.blockRanges.itervalues():
333 307 intersection = NSIntersectionRange(r,textRange)
334 308 if(intersection.length == 0): #ranges don't intersect
335 309 if r.location >= textRange.location:
336 310 r.location += len(string)
337 311 else: #ranges intersect
338 312 if(r.location <= textRange.location):
339 313 assert(intersection.length == textRange.length)
340 314 r.length += textRange.length
341 315 else:
342 316 r.location += intersection.length
343 317
344 318 self.textView.replaceCharactersInRange_withString_(
345 319 textRange, string)
346 320 self.textView.setSelectedRange_(
347 321 NSMakeRange(textRange.location+len(string), 0))
348 322 if(scrollToVisible):
349 323 self.textView.scrollRangeToVisible_(textRange)
350 324
351 325
352 326
353 327
354 328 def replace_current_block_with_string(self, textView, string):
355 329 textView.replaceCharactersInRange_withString_(
356 330 self.current_block_range(),
357 331 string)
358 332 self.current_block_range().length = len(string)
359 333 r = NSMakeRange(textView.textStorage().length(), 0)
360 334 textView.scrollRangeToVisible_(r)
361 335 textView.setSelectedRange_(r)
362 336
363 337
364 338 def current_indent_string(self):
365 339 """returns string for indent or None if no indent"""
366 340
367 341 return self._indent_for_block(self.current_block())
368 342
369 343
370 344 def _indent_for_block(self, block):
371 345 lines = block.split('\n')
372 346 if(len(lines) > 1):
373 347 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
374 348 if(currentIndent == 0):
375 349 currentIndent = self.tabSpaces
376 350
377 351 if(self.tabUsesSpaces):
378 352 result = ' ' * currentIndent
379 353 else:
380 354 result = '\t' * (currentIndent/self.tabSpaces)
381 355 else:
382 356 result = None
383 357
384 358 return result
385 359
386 360
387 361 # NSTextView delegate methods...
388 362 def textView_doCommandBySelector_(self, textView, selector):
389 363 assert(textView == self.textView)
390 364 NSLog("textView_doCommandBySelector_: "+selector)
391 365
392 366
393 367 if(selector == 'insertNewline:'):
394 368 indent = self.current_indent_string()
395 369 if(indent):
396 370 line = indent + self.current_line()
397 371 else:
398 372 line = self.current_line()
399 373
400 374 if(self.is_complete(self.current_block())):
401 375 self.execute(self.current_block(),
402 376 blockID=self.currentBlockID)
403 377 self.start_new_block()
404 378
405 379 return True
406 380
407 381 return False
408 382
409 383 elif(selector == 'moveUp:'):
410 384 prevBlock = self.get_history_previous(self.current_block())
411 385 if(prevBlock != None):
412 386 self.replace_current_block_with_string(textView, prevBlock)
413 387 else:
414 388 NSBeep()
415 389 return True
416 390
417 391 elif(selector == 'moveDown:'):
418 392 nextBlock = self.get_history_next()
419 393 if(nextBlock != None):
420 394 self.replace_current_block_with_string(textView, nextBlock)
421 395 else:
422 396 NSBeep()
423 397 return True
424 398
425 399 elif(selector == 'moveToBeginningOfParagraph:'):
426 400 textView.setSelectedRange_(NSMakeRange(
427 401 self.current_block_range().location,
428 402 0))
429 403 return True
430 404 elif(selector == 'moveToEndOfParagraph:'):
431 405 textView.setSelectedRange_(NSMakeRange(
432 406 self.current_block_range().location + \
433 407 self.current_block_range().length, 0))
434 408 return True
435 409 elif(selector == 'deleteToEndOfParagraph:'):
436 410 if(textView.selectedRange().location <= \
437 411 self.current_block_range().location):
438 412 # Intersect the selected range with the current line range
439 413 if(self.current_block_range().length < 0):
440 414 self.blockRanges[self.currentBlockID].length = 0
441 415
442 416 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
443 417 self.current_block_range())
444 418
445 419 if(r.length > 0): #no intersection
446 420 textView.setSelectedRange_(r)
447 421
448 422 return False # don't actually handle the delete
449 423
450 424 elif(selector == 'insertTab:'):
451 425 if(len(self.current_line().strip()) == 0): #only white space
452 426 return False
453 427 else:
454 428 self.textView.complete_(self)
455 429 return True
456 430
457 431 elif(selector == 'deleteBackward:'):
458 432 #if we're at the beginning of the current block, ignore
459 433 if(textView.selectedRange().location == \
460 434 self.current_block_range().location):
461 435 return True
462 436 else:
463 437 self.current_block_range().length-=1
464 438 return False
465 439 return False
466 440
467 441
468 442 def textView_shouldChangeTextInRanges_replacementStrings_(self,
469 443 textView, ranges, replacementStrings):
470 444 """
471 445 Delegate method for NSTextView.
472 446
473 447 Refuse change text in ranges not at end, but make those changes at
474 448 end.
475 449 """
476 450
477 451 assert(len(ranges) == len(replacementStrings))
478 452 allow = True
479 453 for r,s in zip(ranges, replacementStrings):
480 454 r = r.rangeValue()
481 455 if(textView.textStorage().length() > 0 and
482 456 r.location < self.current_block_range().location):
483 457 self.insert_text(s)
484 458 allow = False
485 459
486 460
487 461 self.blockRanges.setdefault(self.currentBlockID,
488 462 self.current_block_range()).length +=\
489 463 len(s)
490 464
491 465 return allow
492 466
493 467 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
494 468 textView, words, charRange, index):
495 469 try:
496 470 ts = textView.textStorage()
497 471 token = ts.string().substringWithRange_(charRange)
498 472 completions = blockingCallFromThread(self.complete, token)
499 473 except:
500 474 completions = objc.nil
501 475 NSBeep()
502 476
503 477 return (completions,0)
504 478
505 479
@@ -1,407 +1,403 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 3 """
4 4 frontendbase provides an interface and base class for GUI frontends for
5 5 IPython.kernel/IPython.kernel.core.
6 6
7 7 Frontend implementations will likely want to subclass FrontEndBase.
8 8
9 9 Author: Barry Wark
10 10 """
11 11 __docformat__ = "restructuredtext en"
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import string
24 24 import uuid
25 25 import _ast
26 26
27 27 try:
28 28 from zope.interface import Interface, Attribute, implements, classProvides
29 29 except ImportError:
30 30 #zope.interface is not available
31 31 Interface = object
32 32 def Attribute(name, doc): pass
33 33 def implements(interface): pass
34 34 def classProvides(interface): pass
35 35
36 36 from IPython.kernel.core.history import FrontEndHistory
37 37 from IPython.kernel.core.util import Bunch
38 38 from IPython.kernel.engineservice import IEngineCore
39 39
40 try:
41 from twisted.python.failure import Failure
42 except ImportError:
43 #Twisted not available
44 Failure = Exception
45 40
46 41 ##############################################################################
47 42 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
48 43 # not
49 44
50 45 rc = Bunch()
51 46 rc.prompt_in1 = r'In [$number]: '
52 47 rc.prompt_in2 = r'...'
53 48 rc.prompt_out = r'Out [$number]: '
54 49
55 50 ##############################################################################
56 51
57 52 class IFrontEndFactory(Interface):
58 53 """Factory interface for frontends."""
59 54
60 55 def __call__(engine=None, history=None):
61 56 """
62 57 Parameters:
63 58 interpreter : IPython.kernel.engineservice.IEngineCore
64 59 """
65 60
66 61 pass
67 62
68 63
69 64
70 65 class IFrontEnd(Interface):
71 66 """Interface for frontends. All methods return t.i.d.Deferred"""
72 67
73 68 Attribute("input_prompt_template", "string.Template instance\
74 69 substituteable with execute result.")
75 70 Attribute("output_prompt_template", "string.Template instance\
76 71 substituteable with execute result.")
77 72 Attribute("continuation_prompt_template", "string.Template instance\
78 73 substituteable with execute result.")
79 74
80 75 def update_cell_prompt(result, blockID=None):
81 76 """Subclass may override to update the input prompt for a block.
82 77 Since this method will be called as a
83 78 twisted.internet.defer.Deferred's callback/errback,
84 79 implementations should return result when finished.
85 80
86 81 Result is a result dict in case of success, and a
87 82 twisted.python.util.failure.Failure in case of an error
88 83 """
89 84
90 85 pass
91 86
92 87
93 88 def render_result(result):
94 89 """Render the result of an execute call. Implementors may choose the
95 90 method of rendering.
96 91 For example, a notebook-style frontend might render a Chaco plot
97 92 inline.
98 93
99 94 Parameters:
100 95 result : dict (result of IEngineBase.execute )
101 96 blockID = result['blockID']
102 97
103 98 Result:
104 99 Output of frontend rendering
105 100 """
106 101
107 102 pass
108 103
109 104 def render_error(failure):
110 105 """Subclasses must override to render the failure. Since this method
111 106 will be called as a twisted.internet.defer.Deferred's callback,
112 107 implementations should return result when finished.
113 108
114 109 blockID = failure.blockID
115 110 """
116 111
117 112 pass
118 113
119 114
120 115 def input_prompt(number=''):
121 116 """Returns the input prompt by subsituting into
122 117 self.input_prompt_template
123 118 """
124 119 pass
125 120
126 121 def output_prompt(number=''):
127 122 """Returns the output prompt by subsituting into
128 123 self.output_prompt_template
129 124 """
130 125
131 126 pass
132 127
133 128 def continuation_prompt():
134 129 """Returns the continuation prompt by subsituting into
135 130 self.continuation_prompt_template
136 131 """
137 132
138 133 pass
139 134
140 135 def is_complete(block):
141 136 """Returns True if block is complete, False otherwise."""
142 137
143 138 pass
144 139
145 140 def compile_ast(block):
146 141 """Compiles block to an _ast.AST"""
147 142
148 143 pass
149 144
150 145
151 146 def get_history_previous(currentBlock):
152 147 """Returns the block previous in the history. Saves currentBlock if
153 148 the history_cursor is currently at the end of the input history"""
154 149 pass
155 150
156 151 def get_history_next():
157 152 """Returns the next block in the history."""
158 153
159 154 pass
160 155
161 156
162 157 class FrontEndBase(object):
163 158 """
164 159 FrontEndBase manages the state tasks for a CLI frontend:
165 160 - Input and output history management
166 161 - Input/continuation and output prompt generation
167 162
168 163 Some issues (due to possibly unavailable engine):
169 164 - How do we get the current cell number for the engine?
170 165 - How do we handle completions?
171 166 """
172 167
173 168 history_cursor = 0
174 169
175 170 current_indent_level = 0
176 171
177 172
178 173 input_prompt_template = string.Template(rc.prompt_in1)
179 174 output_prompt_template = string.Template(rc.prompt_out)
180 175 continuation_prompt_template = string.Template(rc.prompt_in2)
181 176
182 177 def __init__(self, shell=None, history=None):
183 178 self.shell = shell
184 179 if history is None:
185 180 self.history = FrontEndHistory(input_cache=[''])
186 181 else:
187 182 self.history = history
188 183
189 184
190 185 def input_prompt(self, number=''):
191 186 """Returns the current input prompt
192 187
193 188 It would be great to use ipython1.core.prompts.Prompt1 here
194 189 """
195 190 return self.input_prompt_template.safe_substitute({'number':number})
196 191
197 192
198 193 def continuation_prompt(self):
199 194 """Returns the current continuation prompt"""
200 195
201 196 return self.continuation_prompt_template.safe_substitute()
202 197
203 198 def output_prompt(self, number=''):
204 199 """Returns the output prompt for result"""
205 200
206 201 return self.output_prompt_template.safe_substitute({'number':number})
207 202
208 203
209 204 def is_complete(self, block):
210 205 """Determine if block is complete.
211 206
212 207 Parameters
213 208 block : string
214 209
215 210 Result
216 211 True if block can be sent to the engine without compile errors.
217 212 False otherwise.
218 213 """
219 214
220 215 try:
221 216 ast = self.compile_ast(block)
222 217 except:
223 218 return False
224 219
225 220 lines = block.split('\n')
226 221 return (len(lines)==1 or str(lines[-1])=='')
227 222
228 223
229 224 def compile_ast(self, block):
230 225 """Compile block to an AST
231 226
232 227 Parameters:
233 228 block : str
234 229
235 230 Result:
236 231 AST
237 232
238 233 Throws:
239 234 Exception if block cannot be compiled
240 235 """
241 236
242 237 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
243 238
244 239
245 240 def execute(self, block, blockID=None):
246 241 """Execute the block and return the result.
247 242
248 243 Parameters:
249 244 block : {str, AST}
250 245 blockID : any
251 246 Caller may provide an ID to identify this block.
252 247 result['blockID'] := blockID
253 248
254 249 Result:
255 250 Deferred result of self.interpreter.execute
256 251 """
257 252
258 253 if(not self.is_complete(block)):
259 254 raise Exception("Block is not compilable")
260 255
261 256 if(blockID == None):
262 257 blockID = uuid.uuid4() #random UUID
263 258
264 259 try:
265 260 result = self.shell.execute(block)
266 261 except Exception,e:
267 262 e = self._add_block_id_for_failure(e, blockID=blockID)
268 263 e = self.update_cell_prompt(e, blockID=blockID)
269 264 e = self.render_error(e)
270 265 else:
271 266 result = self._add_block_id_for_result(result, blockID=blockID)
272 267 result = self.update_cell_prompt(result, blockID=blockID)
273 268 result = self.render_result(result)
274 269
275 270 return result
276 271
277 272
278 273 def _add_block_id_for_result(self, result, blockID):
279 274 """Add the blockID to result or failure. Unfortunatley, we have to
280 275 treat failures differently than result dicts.
281 276 """
282 277
283 278 result['blockID'] = blockID
284 279
285 280 return result
286 281
287 282 def _add_block_id_for_failure(self, failure, blockID):
288 283 """_add_block_id_for_failure"""
289 284
290 285 failure.blockID = blockID
291 286 return failure
292 287
293 288
294 289 def _add_history(self, result, block=None):
295 290 """Add block to the history"""
296 291
297 292 assert(block != None)
298 293 self.history.add_items([block])
299 294 self.history_cursor += 1
300 295
301 296 return result
302 297
303 298
304 299 def get_history_previous(self, currentBlock):
305 300 """ Returns previous history string and decrement history cursor.
306 301 """
307 302 command = self.history.get_history_item(self.history_cursor - 1)
308 303
309 304 if command is not None:
310 305 if(self.history_cursor == len(self.history.input_cache)):
311 306 self.history.input_cache[self.history_cursor] = currentBlock
312 307 self.history_cursor -= 1
313 308 return command
314 309
315 310
316 311 def get_history_next(self):
317 312 """ Returns next history string and increment history cursor.
318 313 """
319 314 command = self.history.get_history_item(self.history_cursor+1)
320 315
321 316 if command is not None:
322 317 self.history_cursor += 1
323 318 return command
324 319
325 320 ###
326 321 # Subclasses probably want to override these methods...
327 322 ###
328 323
329 324 def update_cell_prompt(self, result, blockID=None):
330 325 """Subclass may override to update the input prompt for a block.
331 326 Since this method will be called as a
332 327 twisted.internet.defer.Deferred's callback, implementations should
333 328 return result when finished.
334 329 """
335 330
336 331 return result
337 332
338 333
339 334 def render_result(self, result):
340 335 """Subclasses must override to render result. Since this method will
341 336 be called as a twisted.internet.defer.Deferred's callback,
342 337 implementations should return result when finished.
343 338 """
344 339
345 340 return result
346 341
347 342
348 343 def render_error(self, failure):
349 344 """Subclasses must override to render the failure. Since this method
350 345 will be called as a twisted.internet.defer.Deferred's callback,
351 346 implementations should return result when finished.
352 347 """
353 348
354 349 return failure
355 350
356 351
357 352
358 353 class AsyncFrontEndBase(FrontEndBase):
359 354 """
360 355 Overrides FrontEndBase to wrap execute in a deferred result.
361 356 All callbacks are made as callbacks on the deferred result.
362 357 """
363 358
364 359 implements(IFrontEnd)
365 360 classProvides(IFrontEndFactory)
366 361
367 362 def __init__(self, engine=None, history=None):
368 363 assert(engine==None or IEngineCore.providedBy(engine))
369 364 self.engine = IEngineCore(engine)
370 365 if history is None:
371 366 self.history = FrontEndHistory(input_cache=[''])
372 367 else:
373 368 self.history = history
374 369
375 370
376 371 def execute(self, block, blockID=None):
377 372 """Execute the block and return the deferred result.
378 373
379 374 Parameters:
380 375 block : {str, AST}
381 376 blockID : any
382 377 Caller may provide an ID to identify this block.
383 378 result['blockID'] := blockID
384 379
385 380 Result:
386 381 Deferred result of self.interpreter.execute
387 382 """
388 383
389 384 if(not self.is_complete(block)):
385 from twisted.python.failure import Failure
390 386 return Failure(Exception("Block is not compilable"))
391 387
392 388 if(blockID == None):
393 389 blockID = uuid.uuid4() #random UUID
394 390
395 391 d = self.engine.execute(block)
396 392 d.addCallback(self._add_history, block=block)
397 393 d.addCallbacks(self._add_block_id_for_result,
398 394 errback=self._add_block_id_for_failure,
399 395 callbackArgs=(blockID,),
400 396 errbackArgs=(blockID,))
401 397 d.addBoth(self.update_cell_prompt, blockID=blockID)
402 398 d.addCallbacks(self.render_result,
403 399 errback=self.render_error)
404 400
405 401 return d
406 402
407 403
General Comments 0
You need to be logged in to leave comments. Login now