Skip to content

Commit 96e5d07

Browse files
committed
Made input and output stream for aiorepl configurable, removed raw repl
1 parent 66fa62b commit 96e5d07

File tree

1 file changed

+75
-128
lines changed

1 file changed

+75
-128
lines changed

micropython/aiorepl/aiorepl.py

+75-128
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import sys
77
import time
88
import asyncio
9+
import io
10+
import os
911

1012
# Import statement (needs to be global, and does not return).
1113
_RE_IMPORT = re.compile("^import ([^ ]+)( as ([^ ]+))?")
@@ -25,8 +27,19 @@
2527
CHAR_CTRL_D = const(4)
2628
CHAR_CTRL_E = const(5)
2729

30+
# Needed to enable terminal duplication with os.dupterm
31+
class OutputStream(io.IOBase):
32+
def __init__(self, stream):
33+
self.stream = stream
34+
35+
def write(self, data):
36+
self.stream.write(data)
37+
38+
def readinto(self, buf):
39+
return None
2840

29-
async def execute(code, g, s):
41+
42+
async def execute(code, g, in_stream, out_stream):
3043
if not code.strip():
3144
return
3245

@@ -48,9 +61,9 @@ async def __code():
4861
__exec_task = asyncio.create_task(__code())
4962
""".format(code)
5063

51-
async def kbd_intr_task(exec_task, s):
64+
async def kbd_intr_task(exec_task, in_stream):
5265
while True:
53-
if ord(await s.read(1)) == CHAR_CTRL_C:
66+
if ord(await in_stream.read(1)) == CHAR_CTRL_C:
5467
exec_task.cancel()
5568
return
5669

@@ -60,21 +73,31 @@ async def kbd_intr_task(exec_task, s):
6073

6174
# Concurrently wait for either Ctrl-C from the stream or task
6275
# completion.
63-
intr_task = asyncio.create_task(kbd_intr_task(exec_task, s))
76+
intr_task = asyncio.create_task(kbd_intr_task(exec_task, in_stream))
6477

78+
prev = None
79+
if out_stream != sys.stdout:
80+
prev = os.dupterm(out_stream)
81+
6582
try:
6683
try:
6784
return await exec_task
6885
except asyncio.CancelledError:
6986
pass
7087
finally:
88+
os.dupterm(prev)
89+
7190
intr_task.cancel()
7291
try:
7392
await intr_task
7493
except asyncio.CancelledError:
7594
pass
7695
else:
7796
# Excute code snippet directly.
97+
prev = None
98+
if out_stream != sys.stdout:
99+
prev = os.dupterm(out_stream)
100+
78101
try:
79102
try:
80103
micropython.kbd_intr(3)
@@ -86,20 +109,31 @@ async def kbd_intr_task(exec_task, s):
86109
except KeyboardInterrupt:
87110
pass
88111
finally:
112+
os.dupterm(prev)
89113
micropython.kbd_intr(-1)
90114

91115
except Exception as err:
92116
print("{}: {}".format(type(err).__name__, err))
93117

94118

95119
# REPL task. Invoke this with an optional mutable globals dict.
96-
async def task(g=None, prompt="--> "):
120+
# The in_stream should be an object that has an async read method
121+
# The outstream should be an object that has a (non-async) write method
122+
async def task(in_stream=None, out_stream=None, g=None, prompt="--> "):
97123
print("Starting asyncio REPL...")
98124
if g is None:
99125
g = __import__("__main__").__dict__
126+
127+
if in_stream is None:
128+
in_stream = asyncio.StreamReader(sys.stdin)
129+
130+
if out_stream is None:
131+
out_stream = sys.stdout
132+
else:
133+
out_stream = OutputStream(out_stream)
134+
100135
try:
101136
micropython.kbd_intr(-1)
102-
s = asyncio.StreamReader(sys.stdin)
103137
# clear = True
104138
hist = [None] * _HISTORY_LIMIT
105139
hist_i = 0 # Index of most recent entry.
@@ -108,12 +142,12 @@ async def task(g=None, prompt="--> "):
108142
t = 0 # timestamp of most recent character.
109143
while True:
110144
hist_b = 0 # How far back in the history are we currently.
111-
sys.stdout.write(prompt)
145+
out_stream.write(prompt)
112146
cmd: str = ""
113147
paste = False
114148
curs = 0 # cursor offset from end of cmd buffer
115149
while True:
116-
b = await s.read(1)
150+
b = await in_stream.read(1)
117151
pc = c # save previous character
118152
c = ord(b)
119153
pt = t # save previous time
@@ -122,7 +156,7 @@ async def task(g=None, prompt="--> "):
122156
if c == 0x0A:
123157
# LF
124158
if paste:
125-
sys.stdout.write(b)
159+
out_stream.write(b)
126160
cmd += b
127161
continue
128162
# If the previous character was also LF, and was less
@@ -132,194 +166,107 @@ async def task(g=None, prompt="--> "):
132166
continue
133167
if curs:
134168
# move cursor to end of the line
135-
sys.stdout.write("\x1B[{}C".format(curs))
169+
out_stream.write("\x1B[{}C".format(curs))
136170
curs = 0
137-
sys.stdout.write("\n")
171+
out_stream.write("\n")
138172
if cmd:
139173
# Push current command.
140174
hist[hist_i] = cmd
141175
# Increase history length if possible, and rotate ring forward.
142176
hist_n = min(_HISTORY_LIMIT - 1, hist_n + 1)
143177
hist_i = (hist_i + 1) % _HISTORY_LIMIT
144178

145-
result = await execute(cmd, g, s)
179+
result = await execute(cmd, g, in_stream, out_stream)
146180
if result is not None:
147-
sys.stdout.write(repr(result))
148-
sys.stdout.write("\n")
181+
out_stream.write(repr(result))
182+
out_stream.write("\n")
149183
break
150184
elif c == 0x08 or c == 0x7F:
151185
# Backspace.
152186
if cmd:
153187
if curs:
154188
cmd = "".join((cmd[: -curs - 1], cmd[-curs:]))
155-
sys.stdout.write(
189+
out_stream.write(
156190
"\x08\x1B[K"
157191
) # move cursor back, erase to end of line
158-
sys.stdout.write(cmd[-curs:]) # redraw line
159-
sys.stdout.write("\x1B[{}D".format(curs)) # reset cursor location
192+
out_stream.write(cmd[-curs:]) # redraw line
193+
out_stream.write("\x1B[{}D".format(curs)) # reset cursor location
160194
else:
161195
cmd = cmd[:-1]
162-
sys.stdout.write("\x08 \x08")
196+
out_stream.write("\x08 \x08")
163197
elif c == CHAR_CTRL_A:
164-
await raw_repl(s, g)
165-
break
198+
continue
166199
elif c == CHAR_CTRL_B:
167200
continue
168201
elif c == CHAR_CTRL_C:
169202
if paste:
170203
break
171-
sys.stdout.write("\n")
204+
out_stream.write("\n")
172205
break
173206
elif c == CHAR_CTRL_D:
174207
if paste:
175-
result = await execute(cmd, g, s)
208+
result = await execute(cmd, g, in_stream, out_stream)
176209
if result is not None:
177-
sys.stdout.write(repr(result))
178-
sys.stdout.write("\n")
210+
out_stream.write(repr(result))
211+
out_stream.write("\n")
179212
break
180213

181-
sys.stdout.write("\n")
214+
out_stream.write("\n")
182215
# Shutdown asyncio.
183216
asyncio.new_event_loop()
184217
return
185218
elif c == CHAR_CTRL_E:
186-
sys.stdout.write("paste mode; Ctrl-C to cancel, Ctrl-D to finish\n===\n")
219+
out_stream.write("paste mode; Ctrl-C to cancel, Ctrl-D to finish\n===\n")
187220
paste = True
188221
elif c == 0x1B:
189222
# Start of escape sequence.
190-
key = await s.read(2)
223+
key = await in_stream.read(2)
191224
if key in ("[A", "[B"): # up, down
192225
# Stash the current command.
193226
hist[(hist_i - hist_b) % _HISTORY_LIMIT] = cmd
194227
# Clear current command.
195228
b = "\x08" * len(cmd)
196-
sys.stdout.write(b)
197-
sys.stdout.write(" " * len(cmd))
198-
sys.stdout.write(b)
229+
out_stream.write(b)
230+
out_stream.write(" " * len(cmd))
231+
out_stream.write(b)
199232
# Go backwards or forwards in the history.
200233
if key == "[A":
201234
hist_b = min(hist_n, hist_b + 1)
202235
else:
203236
hist_b = max(0, hist_b - 1)
204237
# Update current command.
205238
cmd = hist[(hist_i - hist_b) % _HISTORY_LIMIT]
206-
sys.stdout.write(cmd)
239+
out_stream.write(cmd)
207240
elif key == "[D": # left
208241
if curs < len(cmd) - 1:
209242
curs += 1
210-
sys.stdout.write("\x1B")
211-
sys.stdout.write(key)
243+
out_stream.write("\x1B")
244+
out_stream.write(key)
212245
elif key == "[C": # right
213246
if curs:
214247
curs -= 1
215-
sys.stdout.write("\x1B")
216-
sys.stdout.write(key)
248+
out_stream.write("\x1B")
249+
out_stream.write(key)
217250
elif key == "[H": # home
218251
pcurs = curs
219252
curs = len(cmd)
220-
sys.stdout.write("\x1B[{}D".format(curs - pcurs)) # move cursor left
253+
out_stream.write("\x1B[{}D".format(curs - pcurs)) # move cursor left
221254
elif key == "[F": # end
222255
pcurs = curs
223256
curs = 0
224-
sys.stdout.write("\x1B[{}C".format(pcurs)) # move cursor right
257+
out_stream.write("\x1B[{}C".format(pcurs)) # move cursor right
225258
else:
226-
# sys.stdout.write("\\x")
227-
# sys.stdout.write(hex(c))
259+
# out_stream.write("\\x")
260+
# out_stream.write(hex(c))
228261
pass
229262
else:
230263
if curs:
231264
# inserting into middle of line
232265
cmd = "".join((cmd[:-curs], b, cmd[-curs:]))
233-
sys.stdout.write(cmd[-curs - 1 :]) # redraw line to end
234-
sys.stdout.write("\x1B[{}D".format(curs)) # reset cursor location
266+
out_stream.write(cmd[-curs - 1 :]) # redraw line to end
267+
out_stream.write("\x1B[{}D".format(curs)) # reset cursor location
235268
else:
236-
sys.stdout.write(b)
269+
out_stream.write(b)
237270
cmd += b
238271
finally:
239272
micropython.kbd_intr(3)
240-
241-
242-
async def raw_paste(s, g, window=512):
243-
sys.stdout.write("R\x01") # supported
244-
sys.stdout.write(bytearray([window & 0xFF, window >> 8, 0x01]).decode())
245-
eof = False
246-
idx = 0
247-
buff = bytearray(window)
248-
file = b""
249-
while not eof:
250-
for idx in range(window):
251-
b = await s.read(1)
252-
c = ord(b)
253-
if c == CHAR_CTRL_C or c == CHAR_CTRL_D:
254-
# end of file
255-
sys.stdout.write(chr(CHAR_CTRL_D))
256-
if c == CHAR_CTRL_C:
257-
raise KeyboardInterrupt
258-
file += buff[:idx]
259-
eof = True
260-
break
261-
buff[idx] = c
262-
263-
if not eof:
264-
file += buff
265-
sys.stdout.write("\x01") # indicate window available to host
266-
267-
return file
268-
269-
270-
async def raw_repl(s: asyncio.StreamReader, g: dict):
271-
heading = "raw REPL; CTRL-B to exit\n"
272-
line = ""
273-
sys.stdout.write(heading)
274-
275-
while True:
276-
line = ""
277-
sys.stdout.write(">")
278-
while True:
279-
b = await s.read(1)
280-
c = ord(b)
281-
if c == CHAR_CTRL_A:
282-
rline = line
283-
line = ""
284-
285-
if len(rline) == 2 and ord(rline[0]) == CHAR_CTRL_E:
286-
if rline[1] == "A":
287-
line = await raw_paste(s, g)
288-
break
289-
else:
290-
# reset raw REPL
291-
sys.stdout.write(heading)
292-
sys.stdout.write(">")
293-
continue
294-
elif c == CHAR_CTRL_B:
295-
# exit raw REPL
296-
sys.stdout.write("\n")
297-
return 0
298-
elif c == CHAR_CTRL_C:
299-
# clear line
300-
line = ""
301-
elif c == CHAR_CTRL_D:
302-
# entry finished
303-
# indicate reception of command
304-
sys.stdout.write("OK")
305-
break
306-
else:
307-
# let through any other raw 8-bit value
308-
line += b
309-
310-
if len(line) == 0:
311-
# Normally used to trigger soft-reset but stay in raw mode.
312-
# Fake it for aiorepl / mpremote.
313-
sys.stdout.write("Ignored: soft reboot\n")
314-
sys.stdout.write(heading)
315-
316-
try:
317-
result = exec(line, g)
318-
if result is not None:
319-
sys.stdout.write(repr(result))
320-
sys.stdout.write(chr(CHAR_CTRL_D))
321-
except Exception as ex:
322-
print(line)
323-
sys.stdout.write(chr(CHAR_CTRL_D))
324-
sys.print_exception(ex, sys.stdout)
325-
sys.stdout.write(chr(CHAR_CTRL_D))

0 commit comments

Comments
 (0)