|
2 | 2 |
|
3 | 3 | import argparse |
4 | 4 | import asyncio |
| 5 | +import itertools |
5 | 6 | import os |
6 | 7 | import sys |
7 | 8 | from typing import Generator |
|
16 | 17 |
|
17 | 18 | __all__ = ["main"] |
18 | 19 |
|
| 20 | +# Escape ASCII control characters (0-31 and 128-159) as well as DEL (127). |
| 21 | +# Do not escape NO-BREAK SPACE (160) and SOFT HYPHEN (173), even if Python |
| 22 | +# considers them non-printable, since they don't cause issues in terminal. |
| 23 | + |
| 24 | +# >>> [i for i in range(256) if not any(( |
| 25 | +# ... chr(i).isprintable(), |
| 26 | +# ... i < 32, |
| 27 | +# ... i == 127, |
| 28 | +# ... 128 <= i < 160, |
| 29 | +# ... ))] |
| 30 | +# [160, 173] |
| 31 | + |
| 32 | +TERMINAL_ESCAPES = str.maketrans( |
| 33 | + {i: repr(chr(i))[1:-1] for i in itertools.chain(range(32), range(127, 160))} |
| 34 | +) |
| 35 | + |
| 36 | + |
| 37 | +def escape(string: str) -> str: |
| 38 | + """Make a string safe for a terminal by escaping control characters.""" |
| 39 | + return string.translate(TERMINAL_ESCAPES) |
| 40 | + |
19 | 41 |
|
20 | 42 | def print_during_input(string: str) -> None: |
21 | 43 | sys.stdout.write( |
@@ -81,7 +103,7 @@ def connection_lost(self, exc: Exception | None) -> None: |
81 | 103 | async def print_incoming_messages(websocket: ClientConnection) -> None: |
82 | 104 | async for message in websocket: |
83 | 105 | if isinstance(message, str): |
84 | | - print_during_input("< " + message) |
| 106 | + print_during_input("< " + escape(message)) |
85 | 107 | else: |
86 | 108 | print_during_input("< (binary) " + message.hex()) |
87 | 109 |
|
@@ -139,7 +161,7 @@ async def interactive_client(uri: str) -> None: |
139 | 161 | await websocket.close() |
140 | 162 | assert websocket.close_code is not None and websocket.close_reason is not None |
141 | 163 | close_status = Close(websocket.close_code, websocket.close_reason) |
142 | | - print_over_input(f"Connection closed: {close_status}.") |
| 164 | + print_over_input(f"Connection closed: {escape(str(close_status))}.") |
143 | 165 |
|
144 | 166 |
|
145 | 167 | def main(argv: list[str] | None = None) -> None: |
|
0 commit comments