##// END OF EJS Templates
Oops, had commited leaving some merge junk behind.
gvaroquaux -
Show More
@@ -1,422 +1,359 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 from zopeinterface import Interface, Attribute, implements, classProvides
28 28
29 29 from IPython.kernel.core.history import FrontEndHistory
30 30 from IPython.kernel.core.util import Bunch
31 31 from IPython.kernel.engineservice import IEngineCore
32 32
33 33 ##############################################################################
34 34 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
35 35 # not
36 36
37 37 rc = Bunch()
38 38 rc.prompt_in1 = r'In [$number]: '
39 39 rc.prompt_in2 = r'...'
40 40 rc.prompt_out = r'Out [$number]: '
41 41
42 42 ##############################################################################
43 43 # Interface definitions
44 44 ##############################################################################
45 45
46 46 class IFrontEndFactory(Interface):
47 47 """Factory interface for frontends."""
48 48
49 49 def __call__(engine=None, history=None):
50 50 """
51 51 Parameters:
52 52 interpreter : IPython.kernel.engineservice.IEngineCore
53 53 """
54 54
55 55 pass
56 56
57 57
58 58 class IFrontEnd(Interface):
59 59 """Interface for frontends. All methods return t.i.d.Deferred"""
60 60
61 61 Attribute("input_prompt_template", "string.Template instance\
62 62 substituteable with execute result.")
63 63 Attribute("output_prompt_template", "string.Template instance\
64 64 substituteable with execute result.")
65 65 Attribute("continuation_prompt_template", "string.Template instance\
66 66 substituteable with execute result.")
67 67
68 68 def update_cell_prompt(result, blockID=None):
69 69 """Subclass may override to update the input prompt for a block.
70 70
71 71 In asynchronous frontends, this method will be called as a
72 72 twisted.internet.defer.Deferred's callback/errback.
73 73 Implementations should thus return result when finished.
74 74
75 75 Result is a result dict in case of success, and a
76 76 twisted.python.util.failure.Failure in case of an error
77 77 """
78 78
79 79 pass
80 80
81 81 def render_result(result):
82 82 """Render the result of an execute call. Implementors may choose the
83 83 method of rendering.
84 84 For example, a notebook-style frontend might render a Chaco plot
85 85 inline.
86 86
87 87 Parameters:
88 88 result : dict (result of IEngineBase.execute )
89 89 blockID = result['blockID']
90 90
91 91 Result:
92 92 Output of frontend rendering
93 93 """
94 94
95 95 pass
96 96
97 97 def render_error(failure):
98 98 """Subclasses must override to render the failure.
99 99
100 100 In asynchronous frontend, since this method will be called as a
101 101 twisted.internet.defer.Deferred's callback. Implementations
102 102 should thus return result when finished.
103 103
104 104 blockID = failure.blockID
105 105 """
106 106
107 107 pass
108 108
109 109 def input_prompt(number=''):
110 110 """Returns the input prompt by subsituting into
111 111 self.input_prompt_template
112 112 """
113 113 pass
114 114
115 115 def output_prompt(number=''):
116 116 """Returns the output prompt by subsituting into
117 117 self.output_prompt_template
118 118 """
119 119
120 120 pass
121 121
122 122 def continuation_prompt():
123 123 """Returns the continuation prompt by subsituting into
124 124 self.continuation_prompt_template
125 125 """
126 126
127 127 pass
128 128
129 129 def is_complete(block):
130 130 """Returns True if block is complete, False otherwise."""
131 131
132 132 pass
133 133
134 134 def compile_ast(block):
135 135 """Compiles block to an _ast.AST"""
136 136
137 137 pass
138 138
139 139 def get_history_previous(current_block):
140 140 """Returns the block previous in the history. Saves currentBlock if
141 141 the history_cursor is currently at the end of the input history"""
142 142 pass
143 143
144 144 def get_history_next():
145 145 """Returns the next block in the history."""
146 146
147 147 pass
148 148
149 149 def complete(self, line):
150 150 """Returns the list of possible completions, and the completed
151 151 line.
152 152
153 153 The input argument is the full line to be completed. This method
154 154 returns both the line completed as much as possible, and the list
155 155 of further possible completions (full words).
156 156 """
157 157 pass
158 158
159 159
160 160 ##############################################################################
161 161 # Base class for all the frontends.
162 162 ##############################################################################
163 163
164 164 class FrontEndBase(object):
165 165 """
166 166 FrontEndBase manages the state tasks for a CLI frontend:
167 167 - Input and output history management
168 168 - Input/continuation and output prompt generation
169 169
170 170 Some issues (due to possibly unavailable engine):
171 171 - How do we get the current cell number for the engine?
172 172 - How do we handle completions?
173 173 """
174 174
175 175 history_cursor = 0
176 176
177 177 input_prompt_template = string.Template(rc.prompt_in1)
178 178 output_prompt_template = string.Template(rc.prompt_out)
179 179 continuation_prompt_template = string.Template(rc.prompt_in2)
180 180
181 181 def __init__(self, shell=None, history=None):
182 182 self.shell = shell
183 183 if history is None:
184 184 self.history = FrontEndHistory(input_cache=[''])
185 185 else:
186 186 self.history = history
187 187
188 188
189 189 def input_prompt(self, number=''):
190 190 """Returns the current input prompt
191 191
192 192 It would be great to use ipython1.core.prompts.Prompt1 here
193 193 """
194 194 return self.input_prompt_template.safe_substitute({'number':number})
195 195
196 196
197 197 def continuation_prompt(self):
198 198 """Returns the current continuation prompt"""
199 199
200 200 return self.continuation_prompt_template.safe_substitute()
201 201
202 202 def output_prompt(self, number=''):
203 203 """Returns the output prompt for result"""
204 204
205 205 return self.output_prompt_template.safe_substitute({'number':number})
206 206
207 207
208 208 def is_complete(self, block):
209 209 """Determine if block is complete.
210 210
211 211 Parameters
212 212 block : string
213 213
214 214 Result
215 215 True if block can be sent to the engine without compile errors.
216 216 False otherwise.
217 217 """
218 218
219 219 try:
220 220 ast = self.compile_ast(block)
221 221 except:
222 222 return False
223 223
224 224 lines = block.split('\n')
225 225 return (len(lines)==1 or str(lines[-1])=='')
226 226
227 227
228 228 def compile_ast(self, block):
229 229 """Compile block to an AST
230 230
231 231 Parameters:
232 232 block : str
233 233
234 234 Result:
235 235 AST
236 236
237 237 Throws:
238 238 Exception if block cannot be compiled
239 239 """
240 240
241 241 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
242 242
243 243
244 244 def execute(self, block, blockID=None):
245 245 """Execute the block and return the result.
246 246
247 247 Parameters:
248 248 block : {str, AST}
249 249 blockID : any
250 250 Caller may provide an ID to identify this block.
251 251 result['blockID'] := blockID
252 252
253 253 Result:
254 254 Deferred result of self.interpreter.execute
255 255 """
256 256
257 257 if(not self.is_complete(block)):
258 258 raise Exception("Block is not compilable")
259 259
260 260 if(blockID == None):
261 261 blockID = uuid.uuid4() #random UUID
262 262
263 263 try:
264 264 result = self.shell.execute(block)
265 265 except Exception,e:
266 266 e = self._add_block_id_for_failure(e, blockID=blockID)
267 267 e = self.update_cell_prompt(e, blockID=blockID)
268 268 e = self.render_error(e)
269 269 else:
270 270 result = self._add_block_id_for_result(result, blockID=blockID)
271 271 result = self.update_cell_prompt(result, blockID=blockID)
272 272 result = self.render_result(result)
273 273
274 274 return result
275 275
276 276
277 277 def _add_block_id_for_result(self, result, blockID):
278 278 """Add the blockID to result or failure. Unfortunatley, we have to
279 279 treat failures differently than result dicts.
280 280 """
281 281
282 282 result['blockID'] = blockID
283 283
284 284 return result
285 285
286 286 def _add_block_id_for_failure(self, failure, blockID):
287 287 """_add_block_id_for_failure"""
288 288 failure.blockID = blockID
289 289 return failure
290 290
291 291
292 292 def _add_history(self, result, block=None):
293 293 """Add block to the history"""
294 294
295 295 assert(block != None)
296 296 self.history.add_items([block])
297 297 self.history_cursor += 1
298 298
299 299 return result
300 300
301 301
302 302 def get_history_previous(self, current_block):
303 303 """ Returns previous history string and decrement history cursor.
304 304 """
305 305 command = self.history.get_history_item(self.history_cursor - 1)
306 306
307 307 if command is not None:
308 308 if(self.history_cursor+1 == len(self.history.input_cache)):
309 309 self.history.input_cache[self.history_cursor] = current_block
310 310 self.history_cursor -= 1
311 311 return command
312 312
313 313
314 314 def get_history_next(self):
315 315 """ Returns next history string and increment history cursor.
316 316 """
317 317 command = self.history.get_history_item(self.history_cursor+1)
318 318
319 319 if command is not None:
320 320 self.history_cursor += 1
321 321 return command
322 322
323 323 ###
324 324 # Subclasses probably want to override these methods...
325 325 ###
326 326
327 327 def update_cell_prompt(self, result, blockID=None):
328 328 """Subclass may override to update the input prompt for a block.
329 329
330 330 This method only really makes sens in asyncrhonous frontend.
331 331 Since this method will be called as a
332 332 twisted.internet.defer.Deferred's callback, implementations should
333 333 return result when finished.
334 334 """
335 335
336 336 raise NotImplementedError
337 337
338 338
339 339 def render_result(self, result):
340 340 """Subclasses must override to render result.
341 341
342 342 In asynchronous frontends, this method will be called as a
343 343 twisted.internet.defer.Deferred's callback. Implementations
344 344 should thus return result when finished.
345 345 """
346 346
347 347 raise NotImplementedError
348 348
349 349
350 350 def render_error(self, failure):
351 <<<<<<< TREE
352 351 """Subclasses must override to render the failure.
353 352
354 353 In asynchronous frontends, this method will be called as a
355 354 twisted.internet.defer.Deferred's callback. Implementations
356 355 should thus return result when finished.
357 356 """
358 357
359 358 raise NotImplementedError
360
361
362
363 =======
364 """Subclasses must override to render the failure. Since this method
365 will be called as a twisted.internet.defer.Deferred's callback,
366 implementations should return result when finished.
367 """
368
369 return failure
370
371
372
373 class AsyncFrontEndBase(FrontEndBase):
374 """
375 Overrides FrontEndBase to wrap execute in a deferred result.
376 All callbacks are made as callbacks on the deferred result.
377 """
378
379 implements(IFrontEnd)
380 classProvides(IFrontEndFactory)
381
382 def __init__(self, engine=None, history=None):
383 assert(engine==None or IEngineCore.providedBy(engine))
384 self.engine = IEngineCore(engine)
385 if history is None:
386 self.history = FrontEndHistory(input_cache=[''])
387 else:
388 self.history = history
389
390
391 def execute(self, block, blockID=None):
392 """Execute the block and return the deferred result.
393
394 Parameters:
395 block : {str, AST}
396 blockID : any
397 Caller may provide an ID to identify this block.
398 result['blockID'] := blockID
399
400 Result:
401 Deferred result of self.interpreter.execute
402 """
403
404 if(not self.is_complete(block)):
405 from twisted.python.failure import Failure
406 return Failure(Exception("Block is not compilable"))
407
408 if(blockID == None):
409 blockID = uuid.uuid4() #random UUID
410
411 d = self.engine.execute(block)
412 d.addCallback(self._add_history, block=block)
413 d.addCallback(self._add_block_id_for_result, blockID)
414 d.addErrback(self._add_block_id_for_failure, blockID)
415 d.addBoth(self.update_cell_prompt, blockID=blockID)
416 d.addCallbacks(self.render_result,
417 errback=self.render_error)
418
419 return d
420
421 >>>>>>> MERGE-SOURCE
422 359
General Comments 0
You need to be logged in to leave comments. Login now