File: frontendbase.py

package info (click to toggle)
ipython 0.13.1-2%2Bdeb7u1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 15,752 kB
  • sloc: python: 69,537; makefile: 355; lisp: 272; sh: 80; objc: 37
file content (343 lines) | stat: -rw-r--r-- 10,373 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# encoding: utf-8
# -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
"""
frontendbase provides an interface and base class for GUI frontends for
IPython.kernel/IPython.kernel.core.

Frontend implementations will likely want to subclass FrontEndBase.

Author: Barry Wark
"""
__docformat__ = "restructuredtext en"

#-------------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  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 codeop
import uuid


from IPython.frontend.zopeinterface import (
    Interface,
    Attribute,
)
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]:  '

##############################################################################
# Interface definitions
##############################################################################

class IFrontEndFactory(Interface):
    """Factory interface for frontends."""

    def __call__(engine=None, history=None):
        """
        Parameters:
        interpreter : IPython.kernel.engineservice.IEngineCore
        """

        pass


class IFrontEnd(Interface):
    """Interface for frontends. All methods return t.i.d.Deferred"""

    Attribute("input_prompt_template", "string.Template instance\
                substituteable with execute result.")
    Attribute("output_prompt_template", "string.Template instance\
                substituteable with execute result.")
    Attribute("continuation_prompt_template", "string.Template instance\
                substituteable with execute result.")

    def update_cell_prompt(result, blockID=None):
        """Subclass may override to update the input prompt for a block.

        In asynchronous frontends, this method will be called as a
        twisted.internet.defer.Deferred's callback/errback.
        Implementations should thus return result when finished.

        Result is a result dict in case of success, and a
        twisted.python.util.failure.Failure in case of an error
        """

        pass

    def render_result(result):
        """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.

        Parameters:
            result : dict (result of IEngineBase.execute )
                blockID = result['blockID']

        Result:
            Output of frontend rendering
        """

        pass

    def render_error(failure):
        """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.

        blockID = failure.blockID
        """

        pass

    def input_prompt(number=''):
        """Returns the input prompt by subsituting into
        self.input_prompt_template
        """
        pass

    def output_prompt(number=''):
        """Returns the output prompt by subsituting into
        self.output_prompt_template
        """

        pass

    def continuation_prompt():
        """Returns the continuation prompt by subsituting into
        self.continuation_prompt_template
        """

        pass

    def is_complete(block):
        """Returns True if block is complete, False otherwise."""

        pass


    def get_history_previous(current_block):
        """Returns the block previous in  the history. Saves currentBlock if
        the history_cursor is currently at the end of the input history"""
        pass

    def get_history_next():
        """Returns the next block in the history."""

        pass

    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.
##############################################################################

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)

    def __init__(self, shell=None, history=None):
        self.shell = shell
        if history is None:
                self.history = FrontEndHistory(input_cache=[''])
        else:
            self.history = history


    def input_prompt(self, number=''):
        """Returns the current input prompt

        It would be great to use ipython1.core.prompts.Prompt1 here
        """
        return self.input_prompt_template.safe_substitute({'number':number})


    def continuation_prompt(self):
        """Returns the current continuation prompt"""

        return self.continuation_prompt_template.safe_substitute()

    def output_prompt(self, number=''):
        """Returns the output prompt for result"""

        return self.output_prompt_template.safe_substitute({'number':number})


    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:
            is_complete = codeop.compile_command(block.rstrip() + '\n\n',
                            "<string>", "exec")
        except:
            return False

        lines = block.split('\n')
        return ((is_complete is not None)
                    and (len(lines)==1 or str(lines[-1])==''))


    def execute(self, block, blockID=None):
        """Execute the block and return the result.

        Parameters:
            block : {str, AST}
            blockID : any
                Caller may provide an ID to identify this block.
                result['blockID'] := blockID

        Result:
            Deferred result of self.interpreter.execute
        """

        if(not self.is_complete(block)):
            raise Exception("Block is not compilable")

        if(blockID == None):
            blockID = uuid.uuid4()

        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


    def _add_block_id_for_result(self, result, blockID):
        """Add the blockID to result or failure. Unfortunatley, we have to
        treat failures differently than result dicts.
        """

        result['blockID'] = blockID

        return result

    def _add_block_id_for_failure(self, failure, blockID):
        """_add_block_id_for_failure"""
        failure.blockID = blockID
        return failure


    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


    def get_history_previous(self, current_block):
        """ Returns previous history string and decrement history cursor.
        """
        command = self.history.get_history_item(self.history_cursor - 1)

        if command is not None:
            if(self.history_cursor+1 == len(self.history.input_cache)):
                self.history.input_cache[self.history_cursor] = current_block
            self.history_cursor -= 1
        return command


    def get_history_next(self):
        """ Returns next history string and increment history cursor.
        """
        command = self.history.get_history_item(self.history_cursor+1)

        if command is not None:
            self.history_cursor += 1
        return command

    ###
    # Subclasses probably want to override these methods...
    ###

    def update_cell_prompt(self, result, blockID=None):
        """Subclass may override to update the input prompt for a block.

        This method only really makes sens in asyncrhonous frontend.
        Since this method will be called as a
        twisted.internet.defer.Deferred's callback, implementations should
        return result when finished.
        """

        raise NotImplementedError


    def render_result(self, result):
        """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.
        """

        raise NotImplementedError


    def render_error(self, failure):
        """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.
        """

        raise NotImplementedError