~vishvananda/nova/network-refactor

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/conch/manhole.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.conch.test.test_manhole -*-
 
2
# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Line-input oriented interactive interpreter loop.
 
7
 
 
8
Provides classes for handling Python source input and arbitrary output
 
9
interactively from a Twisted application.  Also included is syntax coloring
 
10
code with support for VT102 terminals, control code handling (^C, ^D, ^Q),
 
11
and reasonable handling of Deferreds.
 
12
 
 
13
@author: Jp Calderone
 
14
"""
 
15
 
 
16
import code, sys, StringIO, tokenize
 
17
 
 
18
from twisted.conch import recvline
 
19
 
 
20
from twisted.internet import defer
 
21
from twisted.python.htmlizer import TokenPrinter
 
22
 
 
23
class FileWrapper:
 
24
    """Minimal write-file-like object.
 
25
 
 
26
    Writes are translated into addOutput calls on an object passed to
 
27
    __init__.  Newlines are also converted from network to local style.
 
28
    """
 
29
 
 
30
    softspace = 0
 
31
    state = 'normal'
 
32
 
 
33
    def __init__(self, o):
 
34
        self.o = o
 
35
 
 
36
    def flush(self):
 
37
        pass
 
38
 
 
39
    def write(self, data):
 
40
        self.o.addOutput(data.replace('\r\n', '\n'))
 
41
 
 
42
    def writelines(self, lines):
 
43
        self.write(''.join(lines))
 
44
 
 
45
class ManholeInterpreter(code.InteractiveInterpreter):
 
46
    """Interactive Interpreter with special output and Deferred support.
 
47
 
 
48
    Aside from the features provided by L{code.InteractiveInterpreter}, this
 
49
    class captures sys.stdout output and redirects it to the appropriate
 
50
    location (the Manhole protocol instance).  It also treats Deferreds
 
51
    which reach the top-level specially: each is formatted to the user with
 
52
    a unique identifier and a new callback and errback added to it, each of
 
53
    which will format the unique identifier and the result with which the
 
54
    Deferred fires and then pass it on to the next participant in the
 
55
    callback chain.
 
56
    """
 
57
 
 
58
    numDeferreds = 0
 
59
    def __init__(self, handler, locals=None, filename="<console>"):
 
60
        code.InteractiveInterpreter.__init__(self, locals)
 
61
        self._pendingDeferreds = {}
 
62
        self.handler = handler
 
63
        self.filename = filename
 
64
        self.resetBuffer()
 
65
 
 
66
    def resetBuffer(self):
 
67
        """Reset the input buffer."""
 
68
        self.buffer = []
 
69
 
 
70
    def push(self, line):
 
71
        """Push a line to the interpreter.
 
72
 
 
73
        The line should not have a trailing newline; it may have
 
74
        internal newlines.  The line is appended to a buffer and the
 
75
        interpreter's runsource() method is called with the
 
76
        concatenated contents of the buffer as source.  If this
 
77
        indicates that the command was executed or invalid, the buffer
 
78
        is reset; otherwise, the command is incomplete, and the buffer
 
79
        is left as it was after the line was appended.  The return
 
80
        value is 1 if more input is required, 0 if the line was dealt
 
81
        with in some way (this is the same as runsource()).
 
82
 
 
83
        """
 
84
        self.buffer.append(line)
 
85
        source = "\n".join(self.buffer)
 
86
        more = self.runsource(source, self.filename)
 
87
        if not more:
 
88
            self.resetBuffer()
 
89
        return more
 
90
 
 
91
    def runcode(self, *a, **kw):
 
92
        orighook, sys.displayhook = sys.displayhook, self.displayhook
 
93
        try:
 
94
            origout, sys.stdout = sys.stdout, FileWrapper(self.handler)
 
95
            try:
 
96
                code.InteractiveInterpreter.runcode(self, *a, **kw)
 
97
            finally:
 
98
                sys.stdout = origout
 
99
        finally:
 
100
            sys.displayhook = orighook
 
101
 
 
102
    def displayhook(self, obj):
 
103
        self.locals['_'] = obj
 
104
        if isinstance(obj, defer.Deferred):
 
105
            # XXX Ick, where is my "hasFired()" interface?
 
106
            if hasattr(obj, "result"):
 
107
                self.write(repr(obj))
 
108
            elif id(obj) in self._pendingDeferreds:
 
109
                self.write("<Deferred #%d>" % (self._pendingDeferreds[id(obj)][0],))
 
110
            else:
 
111
                d = self._pendingDeferreds
 
112
                k = self.numDeferreds
 
113
                d[id(obj)] = (k, obj)
 
114
                self.numDeferreds += 1
 
115
                obj.addCallbacks(self._cbDisplayDeferred, self._ebDisplayDeferred,
 
116
                                 callbackArgs=(k, obj), errbackArgs=(k, obj))
 
117
                self.write("<Deferred #%d>" % (k,))
 
118
        elif obj is not None:
 
119
            self.write(repr(obj))
 
120
 
 
121
    def _cbDisplayDeferred(self, result, k, obj):
 
122
        self.write("Deferred #%d called back: %r" % (k, result), True)
 
123
        del self._pendingDeferreds[id(obj)]
 
124
        return result
 
125
 
 
126
    def _ebDisplayDeferred(self, failure, k, obj):
 
127
        self.write("Deferred #%d failed: %r" % (k, failure.getErrorMessage()), True)
 
128
        del self._pendingDeferreds[id(obj)]
 
129
        return failure
 
130
 
 
131
    def write(self, data, async=False):
 
132
        self.handler.addOutput(data, async)
 
133
 
 
134
CTRL_C = '\x03'
 
135
CTRL_D = '\x04'
 
136
CTRL_BACKSLASH = '\x1c'
 
137
CTRL_L = '\x0c'
 
138
 
 
139
class Manhole(recvline.HistoricRecvLine):
 
140
    """Mediator between a fancy line source and an interactive interpreter.
 
141
 
 
142
    This accepts lines from its transport and passes them on to a
 
143
    L{ManholeInterpreter}.  Control commands (^C, ^D, ^\) are also handled
 
144
    with something approximating their normal terminal-mode behavior.  It
 
145
    can optionally be constructed with a dict which will be used as the
 
146
    local namespace for any code executed.
 
147
    """
 
148
 
 
149
    namespace = None
 
150
 
 
151
    def __init__(self, namespace=None):
 
152
        recvline.HistoricRecvLine.__init__(self)
 
153
        if namespace is not None:
 
154
            self.namespace = namespace.copy()
 
155
 
 
156
    def connectionMade(self):
 
157
        recvline.HistoricRecvLine.connectionMade(self)
 
158
        self.interpreter = ManholeInterpreter(self, self.namespace)
 
159
        self.keyHandlers[CTRL_C] = self.handle_INT
 
160
        self.keyHandlers[CTRL_D] = self.handle_EOF
 
161
        self.keyHandlers[CTRL_L] = self.handle_FF
 
162
        self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
 
163
 
 
164
 
 
165
    def handle_INT(self):
 
166
        """
 
167
        Handle ^C as an interrupt keystroke by resetting the current input
 
168
        variables to their initial state.
 
169
        """
 
170
        self.pn = 0
 
171
        self.lineBuffer = []
 
172
        self.lineBufferIndex = 0
 
173
        self.interpreter.resetBuffer()
 
174
 
 
175
        self.terminal.nextLine()
 
176
        self.terminal.write("KeyboardInterrupt")
 
177
        self.terminal.nextLine()
 
178
        self.terminal.write(self.ps[self.pn])
 
179
 
 
180
 
 
181
    def handle_EOF(self):
 
182
        if self.lineBuffer:
 
183
            self.terminal.write('\a')
 
184
        else:
 
185
            self.handle_QUIT()
 
186
 
 
187
 
 
188
    def handle_FF(self):
 
189
        """
 
190
        Handle a 'form feed' byte - generally used to request a screen
 
191
        refresh/redraw.
 
192
        """
 
193
        self.terminal.eraseDisplay()
 
194
        self.terminal.cursorHome()
 
195
        self.drawInputLine()
 
196
 
 
197
 
 
198
    def handle_QUIT(self):
 
199
        self.terminal.loseConnection()
 
200
 
 
201
 
 
202
    def _needsNewline(self):
 
203
        w = self.terminal.lastWrite
 
204
        return not w.endswith('\n') and not w.endswith('\x1bE')
 
205
 
 
206
    def addOutput(self, bytes, async=False):
 
207
        if async:
 
208
            self.terminal.eraseLine()
 
209
            self.terminal.cursorBackward(len(self.lineBuffer) + len(self.ps[self.pn]))
 
210
 
 
211
        self.terminal.write(bytes)
 
212
 
 
213
        if async:
 
214
            if self._needsNewline():
 
215
                self.terminal.nextLine()
 
216
 
 
217
            self.terminal.write(self.ps[self.pn])
 
218
 
 
219
            if self.lineBuffer:
 
220
                oldBuffer = self.lineBuffer
 
221
                self.lineBuffer = []
 
222
                self.lineBufferIndex = 0
 
223
 
 
224
                self._deliverBuffer(oldBuffer)
 
225
 
 
226
    def lineReceived(self, line):
 
227
        more = self.interpreter.push(line)
 
228
        self.pn = bool(more)
 
229
        if self._needsNewline():
 
230
            self.terminal.nextLine()
 
231
        self.terminal.write(self.ps[self.pn])
 
232
 
 
233
class VT102Writer:
 
234
    """Colorizer for Python tokens.
 
235
 
 
236
    A series of tokens are written to instances of this object.  Each is
 
237
    colored in a particular way.  The final line of the result of this is
 
238
    generally added to the output.
 
239
    """
 
240
 
 
241
    typeToColor = {
 
242
        'identifier': '\x1b[31m',
 
243
        'keyword': '\x1b[32m',
 
244
        'parameter': '\x1b[33m',
 
245
        'variable': '\x1b[1;33m',
 
246
        'string': '\x1b[35m',
 
247
        'number': '\x1b[36m',
 
248
        'op': '\x1b[37m'}
 
249
 
 
250
    normalColor = '\x1b[0m'
 
251
 
 
252
    def __init__(self):
 
253
        self.written = []
 
254
 
 
255
    def color(self, type):
 
256
        r = self.typeToColor.get(type, '')
 
257
        return r
 
258
 
 
259
    def write(self, token, type=None):
 
260
        if token and token != '\r':
 
261
            c = self.color(type)
 
262
            if c:
 
263
                self.written.append(c)
 
264
            self.written.append(token)
 
265
            if c:
 
266
                self.written.append(self.normalColor)
 
267
 
 
268
    def __str__(self):
 
269
        s = ''.join(self.written)
 
270
        return s.strip('\n').splitlines()[-1]
 
271
 
 
272
def lastColorizedLine(source):
 
273
    """Tokenize and colorize the given Python source.
 
274
 
 
275
    Returns a VT102-format colorized version of the last line of C{source}.
 
276
    """
 
277
    w = VT102Writer()
 
278
    p = TokenPrinter(w.write).printtoken
 
279
    s = StringIO.StringIO(source)
 
280
 
 
281
    tokenize.tokenize(s.readline, p)
 
282
 
 
283
    return str(w)
 
284
 
 
285
class ColoredManhole(Manhole):
 
286
    """A REPL which syntax colors input as users type it.
 
287
    """
 
288
 
 
289
    def getSource(self):
 
290
        """Return a string containing the currently entered source.
 
291
 
 
292
        This is only the code which will be considered for execution
 
293
        next.
 
294
        """
 
295
        return ('\n'.join(self.interpreter.buffer) +
 
296
                '\n' +
 
297
                ''.join(self.lineBuffer))
 
298
 
 
299
 
 
300
    def characterReceived(self, ch, moreCharactersComing):
 
301
        if self.mode == 'insert':
 
302
            self.lineBuffer.insert(self.lineBufferIndex, ch)
 
303
        else:
 
304
            self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
 
305
        self.lineBufferIndex += 1
 
306
 
 
307
        if moreCharactersComing:
 
308
            # Skip it all, we'll get called with another character in
 
309
            # like 2 femtoseconds.
 
310
            return
 
311
 
 
312
        if ch == ' ':
 
313
            # Don't bother to try to color whitespace
 
314
            self.terminal.write(ch)
 
315
            return
 
316
 
 
317
        source = self.getSource()
 
318
 
 
319
        # Try to write some junk
 
320
        try:
 
321
            coloredLine = lastColorizedLine(source)
 
322
        except tokenize.TokenError:
 
323
            # We couldn't do it.  Strange.  Oh well, just add the character.
 
324
            self.terminal.write(ch)
 
325
        else:
 
326
            # Success!  Clear the source on this line.
 
327
            self.terminal.eraseLine()
 
328
            self.terminal.cursorBackward(len(self.lineBuffer) + len(self.ps[self.pn]) - 1)
 
329
 
 
330
            # And write a new, colorized one.
 
331
            self.terminal.write(self.ps[self.pn] + coloredLine)
 
332
 
 
333
            # And move the cursor to where it belongs
 
334
            n = len(self.lineBuffer) - self.lineBufferIndex
 
335
            if n:
 
336
                self.terminal.cursorBackward(n)