15
14
from threading import Thread
16
15
from functools import partial
20
19
class Interrupt(Exception):
22
Exception.__init__(self, "Interrupted!")
24
22
class ExpiryTimer(object):
25
23
def __init__(self, idle):
62
55
self.remainder = ''
64
def _trim_incomplete_final(self, stuff):
65
'''Trim an incomplete UTF-8 character from the end of a string.
66
Returns (trimmed_string, count_of_trimmed_bytes).
68
tokill = incomplete_utf8_sequence(stuff)
70
return (stuff, tokill)
72
return (stuff[:-tokill], tokill)
74
57
def write(self, stuff):
75
# print will only give a non-file a unicode or str. There's no way
76
# to convince it to encode unicodes, so we have to do it ourselves.
77
# Yay for file special-cases (fileobject.c, PyFile_WriteObject).
78
# If somebody wants to write some other object to here, they do it
80
if isinstance(stuff, unicode):
81
stuff = stuff.encode('utf-8')
82
self.remainder = self.remainder + stuff
84
# if there's less than 128 bytes, buffer
85
if len(self.remainder) < 128:
88
# if there's lots, then send it in 1/2K blocks
89
while len(self.remainder) > 512:
90
# We send things as Unicode inside JSON, so we must only send
91
# complete UTF-8 characters.
92
(blk, count) = self._trim_incomplete_final(self.remainder[:512])
93
self.cmdQ.put({"output":blk.decode('utf-8', 'replace')})
96
self.remainder = self.remainder[512 - count:]
98
# Finally, split the remainder up into lines, and ship all the
99
# completed lines off to the server.
100
lines = self.remainder.split("\n")
58
# Split the content up into lines, and ship all the completed
59
# lines off to the server.
60
lines = stuff.split("\n")
61
lines[0] = self.remainder + lines[0]
101
62
self.remainder = lines[-1]
104
65
if len(lines) > 0:
106
67
text = "\n".join(lines)
107
self.cmdQ.put({"output":text.decode('utf-8', 'replace')})
68
self.cmdQ.put({"output":text})
109
70
ln = self.lineQ.get()
110
71
if 'interrupt' in ln:
114
75
if len(self.remainder) > 0:
115
(out, count) = self._trim_incomplete_final(self.remainder)
116
self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
76
self.cmdQ.put({"output":self.remainder})
118
78
ln = self.lineQ.get()
119
# Leave incomplete characters in the buffer.
120
# Yes, this does mean that an incomplete character will be left
121
# off the end, but we discussed this and it was deemed best.
122
self.remainder = self.remainder[len(self.remainder)-count:]
123
80
if 'interrupt' in ln:
127
"""Provides a file like interface to the Web front end of the console.
128
You may print text to the console using write(), flush any buffered output
129
using flush(), or request text from the console using readline()"""
131
def __init__(self, cmdQ, lineQ):
134
self.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
135
self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
137
def write(self, stuff):
138
self.stdout.write(stuff)
145
return self.stdin.readline()
147
83
class PythonRunner(Thread):
148
84
def __init__(self, cmdQ, lineQ):
150
86
self.lineQ = lineQ
151
self.webio = WebIO(self.cmdQ, self.lineQ)
152
88
Thread.__init__(self)
154
90
def execCmd(self, cmd):
156
sys.stdin = self.webio
157
sys.stdout = self.webio
158
sys.stderr = self.webio
159
# We don't expect a return value - 'single' symbol prints it.
160
eval(cmd, self.globs)
162
self.cmdQ.put({"okay": None})
92
sys.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
93
self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
94
sys.stdout = self.stdout
95
sys.stderr = self.stdout
96
res = eval(cmd, self.globs, self.locls)
98
self.cmdQ.put({"okay":res})
163
99
self.curr_cmd = ''
165
tb = format_exc_start(start=1)
167
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
100
except Exception, exc:
102
self.cmdQ.put({"exc":str(exc)})
168
103
self.curr_cmd = ''
172
107
self.globs['__builtins__'] = globals()['__builtins__']
173
109
self.curr_cmd = ''
110
compiler = codeop.CommandCompiler()
176
113
ln = self.lineQ.get()
181
118
self.curr_cmd = self.curr_cmd + '\n' + ln['chat']
183
cmd = codeop.compile_command(self.curr_cmd, '<web session>')
120
cmd = compiler(self.curr_cmd)
185
122
# The command was incomplete,
186
123
# so send back a None, so the
188
125
self.cmdQ.put({"more":None})
190
127
self.execCmd(cmd)
192
tb = format_exc_start(start=3)
193
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
128
except Exception, exc:
130
self.cmdQ.put({"exc":str(exc)})
195
131
self.curr_cmd = ''
196
132
if 'block' in ln:
197
133
# throw away a partial command.
199
135
cmd = compile(ln['block'], "<web session>", 'exec');
200
136
self.execCmd(cmd)
202
tb = format_exc_start(start=1)
204
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
137
except Exception, exc:
139
self.cmdQ.put({"exc":str(exc)})
205
140
self.curr_cmd = ''
234
169
lineQ.put({msg['cmd']:msg['text']})
235
170
return cmdQ.get()
237
def format_exc_start(start=0):
238
etype, value, tb = sys.exc_info()
239
tbbits = traceback.extract_tb(tb)[start:]
240
list = ['Traceback (most recent call last):\n']
241
list = list + traceback.format_list(tbbits)
242
list = list + traceback.format_exception_only(etype, value)
245
def incomplete_utf8_sequence(byteseq):
248
Given a UTF-8-encoded byte sequence (str), returns the number of bytes at
249
the end of the string which comprise an incomplete UTF-8 character
252
If the string is empty or ends with a complete character OR INVALID
254
Otherwise, returns 1-3 indicating the number of bytes in the final
255
incomplete (but valid) character sequence.
257
Does not check any bytes before the final sequence for correctness.
259
>>> incomplete_utf8_sequence("")
261
>>> incomplete_utf8_sequence("xy")
263
>>> incomplete_utf8_sequence("xy\xc3\xbc")
265
>>> incomplete_utf8_sequence("\xc3")
267
>>> incomplete_utf8_sequence("\xbc\xc3")
269
>>> incomplete_utf8_sequence("xy\xbc\xc3")
271
>>> incomplete_utf8_sequence("xy\xe0\xa0")
273
>>> incomplete_utf8_sequence("xy\xf4")
275
>>> incomplete_utf8_sequence("xy\xf4\x8f")
277
>>> incomplete_utf8_sequence("xy\xf4\x8f\xa0")
282
for b in byteseq[::-1]:
286
# 0xxxxxxx (single-byte character)
289
elif b & 0xc0 == 0x80:
290
# 10xxxxxx (subsequent byte)
292
elif b & 0xe0 == 0xc0:
293
# 110xxxxx (start of 2-byte sequence)
296
elif b & 0xf0 == 0xe0:
297
# 1110xxxx (start of 3-byte sequence)
300
elif b & 0xf8 == 0xf0:
301
# 11110xxx (start of 4-byte sequence)
309
# Seen too many "subsequent bytes", invalid
313
# We never saw a "first byte", invalid
316
# We now know expect and count
318
# Complete, or we saw an invalid sequence
324
172
if __name__ == "__main__":
325
173
port = int(sys.argv[1])
326
174
magic = sys.argv[2]
327
175
if len(sys.argv) >= 4:
329
177
os.chdir(sys.argv[3])
330
# Make python's search path follow the cwd
332
os.environ['HOME'] = sys.argv[3]
334
179
common.chat.start_server(port, magic, True, dispatch_msg, initializer)