frontendbase.py
358 lines
| 10.9 KiB
| text/x-python
|
PythonLexer
Barry Wark
|
r1263 | # encoding: utf-8 | ||
Barry Wark
|
r1291 | # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*- | ||
Barry Wark
|
r1263 | """ | ||
Barry Wark
|
r1291 | frontendbase provides an interface and base class for GUI frontends for | ||
IPython.kernel/IPython.kernel.core. | ||||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1277 | Frontend implementations will likely want to subclass FrontEndBase. | ||
Author: Barry Wark | ||||
Barry Wark
|
r1263 | """ | ||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
Barry Wark
|
r1277 | # Copyright (C) 2008 The IPython Development Team | ||
Barry Wark
|
r1263 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Imports | ||||
#------------------------------------------------------------------------------- | ||||
import string | ||||
import uuid | ||||
Barry Wark
|
r1279 | import _ast | ||
Barry Wark
|
r1263 | |||
gvaroquaux
|
r1455 | from zopeinterface import Interface, Attribute, implements, classProvides | ||
Barry Wark
|
r1263 | |||
from IPython.kernel.core.history import FrontEndHistory | ||||
from IPython.kernel.core.util import Bunch | ||||
############################################################################## | ||||
# TEMPORARY!!! fake configuration, while we decide whether to use tconfig or | ||||
# not | ||||
rc = Bunch() | ||||
rc.prompt_in1 = r'In [$number]: ' | ||||
rc.prompt_in2 = r'...' | ||||
rc.prompt_out = r'Out [$number]: ' | ||||
############################################################################## | ||||
gvaroquaux
|
r1455 | # Interface definitions | ||
############################################################################## | ||||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1313 | class IFrontEndFactory(Interface): | ||
Barry Wark
|
r1277 | """Factory interface for frontends.""" | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1277 | def __call__(engine=None, history=None): | ||
Barry Wark
|
r1263 | """ | ||
Parameters: | ||||
interpreter : IPython.kernel.engineservice.IEngineCore | ||||
""" | ||||
Barry Wark
|
r1277 | |||
Barry Wark
|
r1263 | pass | ||
Barry Wark
|
r1277 | |||
Barry Wark
|
r1313 | class IFrontEnd(Interface): | ||
Barry Wark
|
r1277 | """Interface for frontends. All methods return t.i.d.Deferred""" | ||
Barry Wark
|
r1313 | Attribute("input_prompt_template", "string.Template instance\ | ||
Barry Wark
|
r1291 | substituteable with execute result.") | ||
Barry Wark
|
r1313 | Attribute("output_prompt_template", "string.Template instance\ | ||
Barry Wark
|
r1291 | substituteable with execute result.") | ||
Barry Wark
|
r1313 | Attribute("continuation_prompt_template", "string.Template instance\ | ||
Barry Wark
|
r1291 | substituteable with execute result.") | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1303 | def update_cell_prompt(result, blockID=None): | ||
Barry Wark
|
r1263 | """Subclass may override to update the input prompt for a block. | ||
gvaroquaux
|
r1455 | |||
In asynchronous frontends, this method will be called as a | ||||
twisted.internet.defer.Deferred's callback/errback. | ||||
Implementations should thus return result when finished. | ||||
Barry Wark
|
r1293 | |||
Barry Wark
|
r1303 | Result is a result dict in case of success, and a | ||
twisted.python.util.failure.Failure in case of an error | ||||
Barry Wark
|
r1293 | """ | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1303 | def render_result(result): | ||
Barry Wark
|
r1291 | """Render the result of an execute call. Implementors may choose the | ||
method of rendering. | ||||
For example, a notebook-style frontend might render a Chaco plot | ||||
inline. | ||||
Barry Wark
|
r1263 | |||
Parameters: | ||||
result : dict (result of IEngineBase.execute ) | ||||
Barry Wark
|
r1303 | blockID = result['blockID'] | ||
Barry Wark
|
r1263 | |||
Result: | ||||
Output of frontend rendering | ||||
""" | ||||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1303 | def render_error(failure): | ||
gvaroquaux
|
r1455 | """Subclasses must override to render the failure. | ||
In asynchronous frontend, since this method will be called as a | ||||
twisted.internet.defer.Deferred's callback. Implementations | ||||
should thus return result when finished. | ||||
Barry Wark
|
r1303 | |||
blockID = failure.blockID | ||||
Barry Wark
|
r1291 | """ | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1304 | def input_prompt(number=''): | ||
Barry Wark
|
r1291 | """Returns the input prompt by subsituting into | ||
self.input_prompt_template | ||||
""" | ||||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1304 | def output_prompt(number=''): | ||
Barry Wark
|
r1291 | """Returns the output prompt by subsituting into | ||
self.output_prompt_template | ||||
""" | ||||
Barry Wark
|
r1277 | |||
pass | ||||
Barry Wark
|
r1282 | def continuation_prompt(): | ||
Barry Wark
|
r1291 | """Returns the continuation prompt by subsituting into | ||
self.continuation_prompt_template | ||||
""" | ||||
Barry Wark
|
r1277 | |||
pass | ||||
def is_complete(block): | ||||
"""Returns True if block is complete, False otherwise.""" | ||||
pass | ||||
def compile_ast(block): | ||||
"""Compiles block to an _ast.AST""" | ||||
pass | ||||
Gael Varoquaux
|
r1358 | def get_history_previous(current_block): | ||
Barry Wark
|
r1281 | """Returns the block previous in the history. Saves currentBlock if | ||
the history_cursor is currently at the end of the input history""" | ||||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1281 | def get_history_next(): | ||
Barry Wark
|
r1277 | """Returns the next block in the history.""" | ||
pass | ||||
Barry Wark
|
r1263 | |||
gvaroquaux
|
r1455 | def complete(self, line): | ||
"""Returns the list of possible completions, and the completed | ||||
line. | ||||
The input argument is the full line to be completed. This method | ||||
returns both the line completed as much as possible, and the list | ||||
of further possible completions (full words). | ||||
""" | ||||
pass | ||||
############################################################################## | ||||
# Base class for all the frontends. | ||||
############################################################################## | ||||
Barry Wark
|
r1263 | |||
class FrontEndBase(object): | ||||
""" | ||||
FrontEndBase manages the state tasks for a CLI frontend: | ||||
- Input and output history management | ||||
- Input/continuation and output prompt generation | ||||
Some issues (due to possibly unavailable engine): | ||||
- How do we get the current cell number for the engine? | ||||
- How do we handle completions? | ||||
""" | ||||
history_cursor = 0 | ||||
input_prompt_template = string.Template(rc.prompt_in1) | ||||
output_prompt_template = string.Template(rc.prompt_out) | ||||
continuation_prompt_template = string.Template(rc.prompt_in2) | ||||
Barry Wark
|
r1306 | def __init__(self, shell=None, history=None): | ||
self.shell = shell | ||||
Barry Wark
|
r1263 | if history is None: | ||
self.history = FrontEndHistory(input_cache=['']) | ||||
else: | ||||
self.history = history | ||||
Barry Wark
|
r1304 | def input_prompt(self, number=''): | ||
Barry Wark
|
r1263 | """Returns the current input prompt | ||
It would be great to use ipython1.core.prompts.Prompt1 here | ||||
""" | ||||
Barry Wark
|
r1303 | return self.input_prompt_template.safe_substitute({'number':number}) | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1282 | def continuation_prompt(self): | ||
Barry Wark
|
r1263 | """Returns the current continuation prompt""" | ||
return self.continuation_prompt_template.safe_substitute() | ||||
Barry Wark
|
r1304 | def output_prompt(self, number=''): | ||
Barry Wark
|
r1263 | """Returns the output prompt for result""" | ||
Barry Wark
|
r1303 | return self.output_prompt_template.safe_substitute({'number':number}) | ||
Barry Wark
|
r1263 | |||
def is_complete(self, block): | ||||
"""Determine if block is complete. | ||||
Parameters | ||||
block : string | ||||
Result | ||||
True if block can be sent to the engine without compile errors. | ||||
False otherwise. | ||||
""" | ||||
try: | ||||
Barry Wark
|
r1278 | ast = self.compile_ast(block) | ||
Barry Wark
|
r1263 | except: | ||
return False | ||||
Barry Wark
|
r1278 | |||
lines = block.split('\n') | ||||
return (len(lines)==1 or str(lines[-1])=='') | ||||
Barry Wark
|
r1263 | |||
def compile_ast(self, block): | ||||
"""Compile block to an AST | ||||
Parameters: | ||||
block : str | ||||
Result: | ||||
AST | ||||
Throws: | ||||
Exception if block cannot be compiled | ||||
""" | ||||
return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST) | ||||
def execute(self, block, blockID=None): | ||||
Barry Wark
|
r1306 | """Execute the block and return the result. | ||
Barry Wark
|
r1263 | |||
Parameters: | ||||
block : {str, AST} | ||||
blockID : any | ||||
Barry Wark
|
r1291 | Caller may provide an ID to identify this block. | ||
result['blockID'] := blockID | ||||
Barry Wark
|
r1263 | |||
Result: | ||||
Deferred result of self.interpreter.execute | ||||
""" | ||||
Barry Wark
|
r1279 | |||
Barry Wark
|
r1306 | if(not self.is_complete(block)): | ||
raise Exception("Block is not compilable") | ||||
if(blockID == None): | ||||
blockID = uuid.uuid4() #random UUID | ||||
try: | ||||
result = self.shell.execute(block) | ||||
except Exception,e: | ||||
e = self._add_block_id_for_failure(e, blockID=blockID) | ||||
e = self.update_cell_prompt(e, blockID=blockID) | ||||
e = self.render_error(e) | ||||
else: | ||||
result = self._add_block_id_for_result(result, blockID=blockID) | ||||
result = self.update_cell_prompt(result, blockID=blockID) | ||||
result = self.render_result(result) | ||||
return result | ||||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1279 | |||
Barry Wark
|
r1303 | def _add_block_id_for_result(self, result, blockID): | ||
Barry Wark
|
r1291 | """Add the blockID to result or failure. Unfortunatley, we have to | ||
treat failures differently than result dicts. | ||||
Barry Wark
|
r1283 | """ | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1303 | result['blockID'] = blockID | ||
Barry Wark
|
r1263 | |||
return result | ||||
Barry Wark
|
r1303 | def _add_block_id_for_failure(self, failure, blockID): | ||
"""_add_block_id_for_failure""" | ||||
failure.blockID = blockID | ||||
return failure | ||||
Barry Wark
|
r1279 | def _add_history(self, result, block=None): | ||
"""Add block to the history""" | ||||
assert(block != None) | ||||
self.history.add_items([block]) | ||||
self.history_cursor += 1 | ||||
return result | ||||
Barry Wark
|
r1263 | |||
Gael Varoquaux
|
r1358 | def get_history_previous(self, current_block): | ||
Barry Wark
|
r1263 | """ Returns previous history string and decrement history cursor. | ||
""" | ||||
command = self.history.get_history_item(self.history_cursor - 1) | ||||
Barry Wark
|
r1281 | |||
Barry Wark
|
r1263 | if command is not None: | ||
Gael Varoquaux
|
r1358 | if(self.history_cursor+1 == len(self.history.input_cache)): | ||
self.history.input_cache[self.history_cursor] = current_block | ||||
Barry Wark
|
r1263 | self.history_cursor -= 1 | ||
return command | ||||
Barry Wark
|
r1281 | def get_history_next(self): | ||
Barry Wark
|
r1263 | """ Returns next history string and increment history cursor. | ||
""" | ||||
Barry Wark
|
r1281 | command = self.history.get_history_item(self.history_cursor+1) | ||
Barry Wark
|
r1263 | if command is not None: | ||
self.history_cursor += 1 | ||||
return command | ||||
### | ||||
# Subclasses probably want to override these methods... | ||||
### | ||||
Barry Wark
|
r1303 | def update_cell_prompt(self, result, blockID=None): | ||
Barry Wark
|
r1263 | """Subclass may override to update the input prompt for a block. | ||
gvaroquaux
|
r1455 | |||
This method only really makes sens in asyncrhonous frontend. | ||||
Barry Wark
|
r1291 | Since this method will be called as a | ||
twisted.internet.defer.Deferred's callback, implementations should | ||||
return result when finished. | ||||
Barry Wark
|
r1283 | """ | ||
Barry Wark
|
r1263 | |||
Gael Varoquaux
|
r1458 | raise NotImplementedError | ||
Barry Wark
|
r1263 | |||
def render_result(self, result): | ||||
gvaroquaux
|
r1455 | """Subclasses must override to render result. | ||
In asynchronous frontends, this method will be called as a | ||||
twisted.internet.defer.Deferred's callback. Implementations | ||||
should thus return result when finished. | ||||
Barry Wark
|
r1291 | """ | ||
Barry Wark
|
r1263 | |||
Gael Varoquaux
|
r1458 | raise NotImplementedError | ||
Barry Wark
|
r1263 | |||
def render_error(self, failure): | ||||
gvaroquaux
|
r1455 | """Subclasses must override to render the failure. | ||
In asynchronous frontends, this method will be called as a | ||||
twisted.internet.defer.Deferred's callback. Implementations | ||||
should thus return result when finished. | ||||
Barry Wark
|
r1303 | """ | ||
Barry Wark
|
r1263 | |||
Gael Varoquaux
|
r1458 | raise NotImplementedError | ||
Barry Wark
|
r1305 | |||