1
2
3
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 os
17 import sys
18 import cmd
19 import code
20 import termios
21 import tty
22
23 from twisted.conch import recvline
24 from twisted.internet import stdio, defer
25
26 from twisted.conch.insults.insults import ServerProtocol
27
28
30 """Minimal write-file-like object.
31
32 Writes are translated into addOutput calls on an object passed to
33 __init__. Newlines are also converted from network to local style.
34 """
35
36 softspace = 0
37 state = 'normal'
38
41
44
47
49 self.write(''.join(lines))
50
51
53
54 - def __init__(self, handler, namespace=None):
56
57 - def push(self, line):
58 raise NotImplementedError
59
60
62 """Interactive Interpreter with special output and Deferred support.
63
64 Aside from the features provided by L{code.InteractiveInterpreter}, this
65 class captures sys.stdout output and redirects it to the appropriate
66 location (the Manhole protocol instance). It also treats Deferreds
67 which reach the top-level specially: each is formatted to the user with
68 a unique identifier and a new callback and errback added to it, each of
69 which will format the unique identifier and the result with which the
70 Deferred fires and then pass it on to the next participant in the
71 callback chain.
72 """
73
74 numDeferreds = 0
75 buffer = None
76
77 - def __init__(self, handler, locals=None, filename="<console>"):
83
84
85
96
97 - def write(self, data, async=False):
99
100
101
103 """Reset the input buffer."""
104 self.buffer = []
105
106 - def push(self, line):
107 """Push a line to the interpreter.
108
109 The line should not have a trailing newline; it may have
110 internal newlines. The line is appended to a buffer and the
111 interpreter's runsource() method is called with the
112 concatenated contents of the buffer as source. If this
113 indicates that the command was executed or invalid, the buffer
114 is reset; otherwise, the command is incomplete, and the buffer
115 is left as it was after the line was appended. The return
116 value is 1 if more input is required, 0 if the line was dealt
117 with in some way (this is the same as runsource()).
118
119 """
120 self.buffer.append(line)
121 source = "\n".join(self.buffer)
122 more = self.runsource(source, self.filename)
123 if not more:
124 self.resetBuffer()
125 return more
126
127
128
129
131 self.locals['_'] = obj
132 if isinstance(obj, defer.Deferred):
133
134 if hasattr(obj, "result"):
135 self.write(repr(obj))
136 elif id(obj) in self._pendingDeferreds:
137 self.write("<Deferred #%d>" % (
138 self._pendingDeferreds[id(obj)][0], ))
139 else:
140 d = self._pendingDeferreds
141 k = self.numDeferreds
142 d[id(obj)] = (k, obj)
143 self.numDeferreds += 1
144 obj.addCallbacks(
145 self._cbDisplayDeferred, self._ebDisplayDeferred,
146 callbackArgs=(k, obj), errbackArgs=(k, obj))
147 self.write("<Deferred #%d>" % (k, ))
148 elif obj is not None:
149 self.write(repr(obj))
150
152 self.write("Deferred #%d called back: %r" % (k, result), True)
153 del self._pendingDeferreds[id(obj)]
154 return result
155
157 self.write("Deferred #%d failed: %r" % (
158 k, failure.getErrorMessage()), True)
159 del self._pendingDeferreds[id(obj)]
160 return failure
161
162 CTRL_C = '\x03'
163 CTRL_D = '\x04'
164 CTRL_BACKSLASH = '\x1c'
165 CTRL_L = '\x0c'
166
167
168 -class Manhole(recvline.HistoricRecvLine):
169 """Mediator between a fancy line source and an interactive interpreter.
170
171 This accepts lines from its transport and passes them on to a
172 L{ManholeInterpreter}. Control commands (^C, ^D, ^\) are also handled
173 with something approximating their normal terminal-mode behavior. It
174 can optionally be constructed with a dict which will be used as the
175 local namespace for any code executed.
176 """
177
178 namespace = None
179 interpreterClass = ManholeInterpreter
180
187
194
195
196
197
199 self.interpreter = self.interpreterClass(self, self.namespace)
200
202 """
203 Handle ^C as an interrupt keystroke by resetting the current input
204 variables to their initial state.
205 """
206 self.pn = 0
207 self.lineBuffer = []
208 self.lineBufferIndex = 0
209 self.interpreter.resetBuffer()
210
211 self.terminal.nextLine()
212 self.terminal.write("KeyboardInterrupt")
213 self.terminal.nextLine()
214 self.terminal.write(self.ps[self.pn])
215
217 if self.lineBuffer:
218 self.terminal.write('\a')
219 else:
220 self.handle_QUIT()
221
223 """
224 Handle a 'form feed' byte - generally used to request a screen
225 refresh/redraw.
226 """
227 self.terminal.eraseDisplay()
228 self.terminal.cursorHome()
229 self.drawInputLine()
230
233
235 w = self.terminal.lastWrite
236 return not w.endswith('\n') and not w.endswith('\x1bE')
237
239 if async:
240 self.terminal.eraseLine()
241 self.terminal.cursorBackward(
242 len(self.lineBuffer) + len(self.ps[self.pn]))
243
244 self.terminal.write(bytes)
245
246 if async:
247 if self._needsNewline():
248 self.terminal.nextLine()
249
250 self.terminal.write(self.ps[self.pn])
251
252 if self.lineBuffer:
253 oldBuffer = self.lineBuffer
254 self.lineBuffer = []
255 self.lineBufferIndex = 0
256
257 self._deliverBuffer(oldBuffer)
258
260 d = defer.maybeDeferred(self.interpreter.push, line)
261
262 def cb(more):
263 self.pn = bool(more)
264 if self._needsNewline():
265 self.terminal.nextLine()
266 self.terminal.write(self.ps[self.pn])
267 d.addCallback(cb)
268 return d
269
270
271
272
274 cmdClass = None
275
276 _cmd = None
277
278 - def __init__(self, handler, localss=None):
284
285
286
287 - def push(self, line):
288 """
289 This version of push returns a deferred that will fire when the command
290 is done and the interpreter can show the next prompt.
291 """
292
293 assert type(line) is not unicode
294
295 self._cmd = self.cmdClass(stdout=self.handler.terminal)
296
297
298 if hasattr(self._cmd, 'command'):
299 self._cmd.command.getRootCommand()._stdout = self.handler.terminal
300 r = self._cmd.onecmd(line)
301 return r
302
303
304
305
307
308 interpreterClass = CmdInterpreter
309
310 - def __init__(self, namespace=None, connectionLostDeferred=None):
311 """
312 @param connectionLostDeferred: a deferred that will be fired when
313 the connection is lost, with the reason.
314 """
315 Manhole.__init__(self, namespace)
316
317 self.connectionLostDeferred = connectionLostDeferred
318
320 """
321 When the connection is lost, there is nothing more to do. Stop the
322 reactor so that the process can exit.
323
324 Override me for custom behaviour.
325 """
326 if not self.connectionLostDeferred:
327 from twisted.internet import reactor
328 reactor.stop()
329 else:
330 self.connectionLostDeferred.callback(reason)
331
332
333
334
339
340
342
344 self._fd = sys.__stdin__.fileno()
345 self._oldSettings = termios.tcgetattr(self._fd)
346 tty.setraw(self._fd)
347
348 - def connect(self, klass, *args, **kwargs):
352
354 os.system('stty sane')
355
356 print
357 termios.tcsetattr(self._fd, termios.TCSANOW, self._oldSettings)
358
359
360
361
362
363
375
376 if __name__ == '__main__':
377
378
380
381 prompt = 'My Command Prompt >>> '
382
385
387 self.stdout.write('this is a test that returns a deferred\n')
388
389 from twisted.internet import defer
390 d = defer.Deferred()
391
392 def cb(_):
393 self.stdout.write('the deferred fired\n')
394 d.addCallback(cb)
395
396 from twisted.internet import reactor
397 reactor.callLater(1, d.callback, None)
398
399 return d
400
403
405 interpreterClass = MyCmdInterpreter
406
407
408 runWithProtocol(MyManhole)
409