Skip to content

Commit 39ea299

Browse files
Replace aiorepl with source copy
1 parent c15256b commit 39ea299

File tree

3 files changed

+335
-2
lines changed

3 files changed

+335
-2
lines changed

internal_filesystem/lib/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ This /lib folder contains:
33
- mip.install("aiohttp") # easy websockets
44
- mip.install("collections") # used by aiohttp
55
- mip.install("unittest")
6-
- mip.install("aiorepl")
76

8-
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/logging/logging.py version 0.6.2 # for About app
7+
- https://github.com/micropython/micropython-lib/blob/master/micropython/aiorepl/aiorepl.py version 0.2.2 # for asyncio REPL, allowing await expressions
8+
99
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/base64/base64.py version 3.3.6 # for nostr
1010
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/binascii/binascii.py version 2.4.1 # for base64.py
11+
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/logging/logging.py version 0.6.2 # for About app
1112
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/shutil/shutil.py version 0.0.5 # for rmtree()
-3.07 KB
Binary file not shown.

internal_filesystem/lib/aiorepl.py

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
# MIT license; Copyright (c) 2022 Jim Mussared
2+
3+
import micropython
4+
from micropython import const
5+
import re
6+
import sys
7+
import time
8+
import asyncio
9+
10+
# Import statement (needs to be global, and does not return).
11+
_RE_IMPORT = re.compile("^import ([^ ]+)( as ([^ ]+))?")
12+
_RE_FROM_IMPORT = re.compile("^from [^ ]+ import ([^ ]+)( as ([^ ]+))?")
13+
# Global variable assignment.
14+
_RE_GLOBAL = re.compile("^([a-zA-Z0-9_]+) ?=[^=]")
15+
# General assignment expression or import statement (does not return a value).
16+
_RE_ASSIGN = re.compile("[^=]=[^=]")
17+
18+
# Command hist (One reserved slot for the current command).
19+
_HISTORY_LIMIT = const(5 + 1)
20+
21+
22+
CHAR_CTRL_A = const(1)
23+
CHAR_CTRL_B = const(2)
24+
CHAR_CTRL_C = const(3)
25+
CHAR_CTRL_D = const(4)
26+
CHAR_CTRL_E = const(5)
27+
28+
29+
async def execute(code, g, s):
30+
if not code.strip():
31+
return
32+
33+
try:
34+
if "await " in code:
35+
# Execute the code snippet in an async context.
36+
if m := _RE_IMPORT.match(code) or _RE_FROM_IMPORT.match(code):
37+
code = "global {}\n {}".format(m.group(3) or m.group(1), code)
38+
elif m := _RE_GLOBAL.match(code):
39+
code = "global {}\n {}".format(m.group(1), code)
40+
elif not _RE_ASSIGN.search(code):
41+
code = "return {}".format(code)
42+
43+
code = """
44+
import asyncio
45+
async def __code():
46+
{}
47+
48+
__exec_task = asyncio.create_task(__code())
49+
""".format(code)
50+
51+
async def kbd_intr_task(exec_task, s):
52+
while True:
53+
if ord(await s.read(1)) == CHAR_CTRL_C:
54+
exec_task.cancel()
55+
return
56+
57+
l = {"__exec_task": None}
58+
exec(code, g, l)
59+
exec_task = l["__exec_task"]
60+
61+
# Concurrently wait for either Ctrl-C from the stream or task
62+
# completion.
63+
intr_task = asyncio.create_task(kbd_intr_task(exec_task, s))
64+
65+
try:
66+
try:
67+
return await exec_task
68+
except asyncio.CancelledError:
69+
pass
70+
finally:
71+
intr_task.cancel()
72+
try:
73+
await intr_task
74+
except asyncio.CancelledError:
75+
pass
76+
else:
77+
# Execute code snippet directly.
78+
try:
79+
try:
80+
micropython.kbd_intr(3)
81+
try:
82+
return eval(code, g)
83+
except SyntaxError:
84+
# Maybe an assignment, try with exec.
85+
return exec(code, g)
86+
except KeyboardInterrupt:
87+
pass
88+
finally:
89+
micropython.kbd_intr(-1)
90+
91+
except Exception as err:
92+
print("{}: {}".format(type(err).__name__, err))
93+
94+
95+
# REPL task. Invoke this with an optional mutable globals dict.
96+
async def task(g=None, prompt="--> "):
97+
print("Starting asyncio REPL...")
98+
if g is None:
99+
g = __import__("__main__").__dict__
100+
try:
101+
micropython.kbd_intr(-1)
102+
s = asyncio.StreamReader(sys.stdin)
103+
# clear = True
104+
hist = [None] * _HISTORY_LIMIT
105+
hist_i = 0 # Index of most recent entry.
106+
hist_n = 0 # Number of history entries.
107+
c = 0 # ord of most recent character.
108+
t = 0 # timestamp of most recent character.
109+
while True:
110+
hist_b = 0 # How far back in the history are we currently.
111+
sys.stdout.write(prompt)
112+
cmd: str = ""
113+
paste = False
114+
curs = 0 # cursor offset from end of cmd buffer
115+
while True:
116+
b = await s.read(1)
117+
if not b: # Handle EOF/empty read
118+
break
119+
pc = c # save previous character
120+
c = ord(b)
121+
pt = t # save previous time
122+
t = time.ticks_ms()
123+
if c < 0x20 or c > 0x7E:
124+
if c == 0x0A:
125+
# LF
126+
if paste:
127+
sys.stdout.write(b)
128+
cmd += b
129+
continue
130+
# If the previous character was also LF, and was less
131+
# than 20 ms ago, this was likely due to CRLF->LFLF
132+
# conversion, so ignore this linefeed.
133+
if pc == 0x0A and time.ticks_diff(t, pt) < 20:
134+
continue
135+
if curs:
136+
# move cursor to end of the line
137+
sys.stdout.write("\x1b[{}C".format(curs))
138+
curs = 0
139+
sys.stdout.write("\n")
140+
if cmd:
141+
# Push current command.
142+
hist[hist_i] = cmd
143+
# Increase history length if possible, and rotate ring forward.
144+
hist_n = min(_HISTORY_LIMIT - 1, hist_n + 1)
145+
hist_i = (hist_i + 1) % _HISTORY_LIMIT
146+
147+
result = await execute(cmd, g, s)
148+
if result is not None:
149+
sys.stdout.write(repr(result))
150+
sys.stdout.write("\n")
151+
break
152+
elif c == 0x08 or c == 0x7F:
153+
# Backspace.
154+
if cmd:
155+
if curs:
156+
cmd = "".join((cmd[: -curs - 1], cmd[-curs:]))
157+
sys.stdout.write(
158+
"\x08\x1b[K"
159+
) # move cursor back, erase to end of line
160+
sys.stdout.write(cmd[-curs:]) # redraw line
161+
sys.stdout.write("\x1b[{}D".format(curs)) # reset cursor location
162+
else:
163+
cmd = cmd[:-1]
164+
sys.stdout.write("\x08 \x08")
165+
elif c == CHAR_CTRL_A:
166+
raw_repl(sys.stdin, g)
167+
break
168+
elif c == CHAR_CTRL_B:
169+
continue
170+
elif c == CHAR_CTRL_C:
171+
if paste:
172+
break
173+
sys.stdout.write("\n")
174+
break
175+
elif c == CHAR_CTRL_D:
176+
if paste:
177+
result = await execute(cmd, g, s)
178+
if result is not None:
179+
sys.stdout.write(repr(result))
180+
sys.stdout.write("\n")
181+
break
182+
183+
sys.stdout.write("\n")
184+
# Shutdown asyncio.
185+
asyncio.new_event_loop()
186+
return
187+
elif c == CHAR_CTRL_E:
188+
sys.stdout.write("paste mode; Ctrl-C to cancel, Ctrl-D to finish\n===\n")
189+
paste = True
190+
elif c == 0x1B:
191+
# Start of escape sequence.
192+
key = await s.read(2)
193+
if key in ("[A", "[B"): # up, down
194+
# Stash the current command.
195+
hist[(hist_i - hist_b) % _HISTORY_LIMIT] = cmd
196+
# Clear current command.
197+
b = "\x08" * len(cmd)
198+
sys.stdout.write(b)
199+
sys.stdout.write(" " * len(cmd))
200+
sys.stdout.write(b)
201+
# Go backwards or forwards in the history.
202+
if key == "[A":
203+
hist_b = min(hist_n, hist_b + 1)
204+
else:
205+
hist_b = max(0, hist_b - 1)
206+
# Update current command.
207+
cmd = hist[(hist_i - hist_b) % _HISTORY_LIMIT]
208+
sys.stdout.write(cmd)
209+
elif key == "[D": # left
210+
if curs < len(cmd) - 1:
211+
curs += 1
212+
sys.stdout.write("\x1b")
213+
sys.stdout.write(key)
214+
elif key == "[C": # right
215+
if curs:
216+
curs -= 1
217+
sys.stdout.write("\x1b")
218+
sys.stdout.write(key)
219+
elif key == "[H": # home
220+
pcurs = curs
221+
curs = len(cmd)
222+
sys.stdout.write("\x1b[{}D".format(curs - pcurs)) # move cursor left
223+
elif key == "[F": # end
224+
pcurs = curs
225+
curs = 0
226+
sys.stdout.write("\x1b[{}C".format(pcurs)) # move cursor right
227+
else:
228+
# sys.stdout.write("\\x")
229+
# sys.stdout.write(hex(c))
230+
pass
231+
else:
232+
if curs:
233+
# inserting into middle of line
234+
cmd = "".join((cmd[:-curs], b, cmd[-curs:]))
235+
sys.stdout.write(cmd[-curs - 1 :]) # redraw line to end
236+
sys.stdout.write("\x1b[{}D".format(curs)) # reset cursor location
237+
else:
238+
sys.stdout.write(b)
239+
cmd += b
240+
finally:
241+
micropython.kbd_intr(3)
242+
243+
244+
def raw_paste(s, window=512):
245+
sys.stdout.write("R\x01") # supported
246+
sys.stdout.write(bytearray([window & 0xFF, window >> 8, 0x01]).decode())
247+
eof = False
248+
idx = 0
249+
buff = bytearray(window)
250+
file = b""
251+
while not eof:
252+
for idx in range(window):
253+
b = s.read(1)
254+
c = ord(b)
255+
if c == CHAR_CTRL_C or c == CHAR_CTRL_D:
256+
# end of file
257+
sys.stdout.write(chr(CHAR_CTRL_D))
258+
if c == CHAR_CTRL_C:
259+
raise KeyboardInterrupt
260+
file += buff[:idx]
261+
eof = True
262+
break
263+
buff[idx] = c
264+
265+
if not eof:
266+
file += buff
267+
sys.stdout.write("\x01") # indicate window available to host
268+
269+
return file
270+
271+
272+
def raw_repl(s, g: dict):
273+
"""
274+
This function is blocking to prevent other
275+
async tasks from writing to the stdio stream and
276+
breaking the raw repl session.
277+
"""
278+
heading = "raw REPL; CTRL-B to exit\n"
279+
line = ""
280+
sys.stdout.write(heading)
281+
282+
while True:
283+
line = ""
284+
sys.stdout.write(">")
285+
while True:
286+
b = s.read(1)
287+
c = ord(b)
288+
if c == CHAR_CTRL_A:
289+
rline = line
290+
line = ""
291+
292+
if len(rline) == 2 and ord(rline[0]) == CHAR_CTRL_E:
293+
if rline[1] == "A":
294+
line = raw_paste(s)
295+
break
296+
else:
297+
# reset raw REPL
298+
sys.stdout.write(heading)
299+
sys.stdout.write(">")
300+
continue
301+
elif c == CHAR_CTRL_B:
302+
# exit raw REPL
303+
sys.stdout.write("\n")
304+
return 0
305+
elif c == CHAR_CTRL_C:
306+
# clear line
307+
line = ""
308+
elif c == CHAR_CTRL_D:
309+
# entry finished
310+
# indicate reception of command
311+
sys.stdout.write("OK")
312+
break
313+
else:
314+
# let through any other raw 8-bit value
315+
line += b
316+
317+
if len(line) == 0:
318+
# Normally used to trigger soft-reset but stay in raw mode.
319+
# Fake it for aiorepl / mpremote.
320+
sys.stdout.write("Ignored: soft reboot\n")
321+
sys.stdout.write(heading)
322+
323+
try:
324+
result = exec(line, g)
325+
if result is not None:
326+
sys.stdout.write(repr(result))
327+
sys.stdout.write(chr(CHAR_CTRL_D))
328+
except Exception as ex:
329+
print(line)
330+
sys.stdout.write(chr(CHAR_CTRL_D))
331+
sys.print_exception(ex, sys.stdout)
332+
sys.stdout.write(chr(CHAR_CTRL_D))

0 commit comments

Comments
 (0)