Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / conch / manhole.py
1 # -*- test-case-name: twisted.conch.test.test_manhole -*-
2 # Copyright (c) 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 CTRL_A = '\x01'
139 CTRL_E = '\x05'
140
141 class Manhole(recvline.HistoricRecvLine):
142     """Mediator between a fancy line source and an interactive interpreter.
143
144     This accepts lines from its transport and passes them on to a
145     L{ManholeInterpreter}.  Control commands (^C, ^D, ^\) are also handled
146     with something approximating their normal terminal-mode behavior.  It
147     can optionally be constructed with a dict which will be used as the
148     local namespace for any code executed.
149     """
150
151     namespace = None
152
153     def __init__(self, namespace=None):
154         recvline.HistoricRecvLine.__init__(self)
155         if namespace is not None:
156             self.namespace = namespace.copy()
157
158     def connectionMade(self):
159         recvline.HistoricRecvLine.connectionMade(self)
160         self.interpreter = ManholeInterpreter(self, self.namespace)
161         self.keyHandlers[CTRL_C] = self.handle_INT
162         self.keyHandlers[CTRL_D] = self.handle_EOF
163         self.keyHandlers[CTRL_L] = self.handle_FF
164         self.keyHandlers[CTRL_A] = self.handle_HOME
165         self.keyHandlers[CTRL_E] = self.handle_END
166         self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
167
168
169     def handle_INT(self):
170         """
171         Handle ^C as an interrupt keystroke by resetting the current input
172         variables to their initial state.
173         """
174         self.pn = 0
175         self.lineBuffer = []
176         self.lineBufferIndex = 0
177         self.interpreter.resetBuffer()
178
179         self.terminal.nextLine()
180         self.terminal.write("KeyboardInterrupt")
181         self.terminal.nextLine()
182         self.terminal.write(self.ps[self.pn])
183
184
185     def handle_EOF(self):
186         if self.lineBuffer:
187             self.terminal.write('\a')
188         else:
189             self.handle_QUIT()
190
191
192     def handle_FF(self):
193         """
194         Handle a 'form feed' byte - generally used to request a screen
195         refresh/redraw.
196         """
197         self.terminal.eraseDisplay()
198         self.terminal.cursorHome()
199         self.drawInputLine()
200
201
202     def handle_QUIT(self):
203         self.terminal.loseConnection()
204
205
206     def _needsNewline(self):
207         w = self.terminal.lastWrite
208         return not w.endswith('\n') and not w.endswith('\x1bE')
209
210     def addOutput(self, bytes, async=False):
211         if async:
212             self.terminal.eraseLine()
213             self.terminal.cursorBackward(len(self.lineBuffer) + len(self.ps[self.pn]))
214
215         self.terminal.write(bytes)
216
217         if async:
218             if self._needsNewline():
219                 self.terminal.nextLine()
220
221             self.terminal.write(self.ps[self.pn])
222
223             if self.lineBuffer:
224                 oldBuffer = self.lineBuffer
225                 self.lineBuffer = []
226                 self.lineBufferIndex = 0
227
228                 self._deliverBuffer(oldBuffer)
229
230     def lineReceived(self, line):
231         more = self.interpreter.push(line)
232         self.pn = bool(more)
233         if self._needsNewline():
234             self.terminal.nextLine()
235         self.terminal.write(self.ps[self.pn])
236
237 class VT102Writer:
238     """Colorizer for Python tokens.
239
240     A series of tokens are written to instances of this object.  Each is
241     colored in a particular way.  The final line of the result of this is
242     generally added to the output.
243     """
244
245     typeToColor = {
246         'identifier': '\x1b[31m',
247         'keyword': '\x1b[32m',
248         'parameter': '\x1b[33m',
249         'variable': '\x1b[1;33m',
250         'string': '\x1b[35m',
251         'number': '\x1b[36m',
252         'op': '\x1b[37m'}
253
254     normalColor = '\x1b[0m'
255
256     def __init__(self):
257         self.written = []
258
259     def color(self, type):
260         r = self.typeToColor.get(type, '')
261         return r
262
263     def write(self, token, type=None):
264         if token and token != '\r':
265             c = self.color(type)
266             if c:
267                 self.written.append(c)
268             self.written.append(token)
269             if c:
270                 self.written.append(self.normalColor)
271
272     def __str__(self):
273         s = ''.join(self.written)
274         return s.strip('\n').splitlines()[-1]
275
276 def lastColorizedLine(source):
277     """Tokenize and colorize the given Python source.
278
279     Returns a VT102-format colorized version of the last line of C{source}.
280     """
281     w = VT102Writer()
282     p = TokenPrinter(w.write).printtoken
283     s = StringIO.StringIO(source)
284
285     tokenize.tokenize(s.readline, p)
286
287     return str(w)
288
289 class ColoredManhole(Manhole):
290     """A REPL which syntax colors input as users type it.
291     """
292
293     def getSource(self):
294         """Return a string containing the currently entered source.
295
296         This is only the code which will be considered for execution
297         next.
298         """
299         return ('\n'.join(self.interpreter.buffer) +
300                 '\n' +
301                 ''.join(self.lineBuffer))
302
303
304     def characterReceived(self, ch, moreCharactersComing):
305         if self.mode == 'insert':
306             self.lineBuffer.insert(self.lineBufferIndex, ch)
307         else:
308             self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
309         self.lineBufferIndex += 1
310
311         if moreCharactersComing:
312             # Skip it all, we'll get called with another character in
313             # like 2 femtoseconds.
314             return
315
316         if ch == ' ':
317             # Don't bother to try to color whitespace
318             self.terminal.write(ch)
319             return
320
321         source = self.getSource()
322
323         # Try to write some junk
324         try:
325             coloredLine = lastColorizedLine(source)
326         except tokenize.TokenError:
327             # We couldn't do it.  Strange.  Oh well, just add the character.
328             self.terminal.write(ch)
329         else:
330             # Success!  Clear the source on this line.
331             self.terminal.eraseLine()
332             self.terminal.cursorBackward(len(self.lineBuffer) + len(self.ps[self.pn]) - 1)
333
334             # And write a new, colorized one.
335             self.terminal.write(self.ps[self.pn] + coloredLine)
336
337             # And move the cursor to where it belongs
338             n = len(self.lineBuffer) - self.lineBufferIndex
339             if n:
340                 self.terminal.cursorBackward(n)