58
62
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)
60
74
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')
61
82
self.remainder = self.remainder + stuff
63
84
# if there's less than 128 bytes, buffer
67
88
# if there's lots, then send it in 1/2K blocks
68
89
while len(self.remainder) > 512:
69
blk = self.remainder[0:512]
70
self.cmdQ.put({"output":blk})
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')})
72
95
ln = self.lineQ.get()
73
self.remainder = self.remainder[512:]
96
self.remainder = self.remainder[512 - count:]
75
98
# Finally, split the remainder up into lines, and ship all the
76
99
# completed lines off to the server.
91
114
if len(self.remainder) > 0:
92
self.cmdQ.put({"output":self.remainder})
115
(out, count) = self._trim_incomplete_final(self.remainder)
116
self.cmdQ.put({"output":out.decode('utf-8', 'replace')})
94
118
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:]
96
123
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()
99
147
class PythonRunner(Thread):
100
148
def __init__(self, cmdQ, lineQ):
102
150
self.lineQ = lineQ
103
self.stdout = StdoutToWeb(self.cmdQ, self.lineQ)
151
self.webio = WebIO(self.cmdQ, self.lineQ)
104
152
Thread.__init__(self)
106
154
def execCmd(self, cmd):
108
sys.stdin = StdinFromWeb(self.cmdQ, self.lineQ)
109
sys.stdout = self.stdout
110
sys.stderr = self.stdout
111
res = eval(cmd, self.globs, self.locls)
113
self.cmdQ.put({"okay":res})
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})
114
163
self.curr_cmd = ''
115
except Exception, exc:
117
self.cmdQ.put({"exc":str(exc)})
165
tb = format_exc_start(start=1)
167
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
118
168
self.curr_cmd = ''
122
172
self.globs['__builtins__'] = globals()['__builtins__']
124
173
self.curr_cmd = ''
125
compiler = codeop.CommandCompiler()
128
176
ln = self.lineQ.get()
133
181
self.curr_cmd = self.curr_cmd + '\n' + ln['chat']
135
cmd = compiler(self.curr_cmd)
183
cmd = codeop.compile_command(self.curr_cmd, '<web session>')
137
185
# The command was incomplete,
138
186
# so send back a None, so the
140
188
self.cmdQ.put({"more":None})
142
190
self.execCmd(cmd)
143
except Exception, exc:
145
self.cmdQ.put({"exc":str(exc)})
192
tb = format_exc_start(start=3)
193
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
146
195
self.curr_cmd = ''
147
196
if 'block' in ln:
148
197
# throw away a partial command.
150
199
cmd = compile(ln['block'], "<web session>", 'exec');
151
200
self.execCmd(cmd)
152
except Exception, exc:
154
self.cmdQ.put({"exc":str(exc)})
202
tb = format_exc_start(start=1)
204
self.cmdQ.put({"exc": ''.join(tb).decode('utf-8', 'replace')})
155
205
self.curr_cmd = ''
184
234
lineQ.put({msg['cmd']:msg['text']})
185
235
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
187
324
if __name__ == "__main__":
188
325
port = int(sys.argv[1])
189
326
magic = sys.argv[2]
190
327
if len(sys.argv) >= 4:
192
329
os.chdir(sys.argv[3])
330
# Make python's search path follow the cwd
193
332
os.environ['HOME'] = sys.argv[3]
195
334
common.chat.start_server(port, magic, True, dispatch_msg, initializer)