Skip to content

Commit ea78a6d

Browse files
committed
Separate stdout and stderr in plain dev
1 parent 8d4dae0 commit ea78a6d

File tree

4 files changed

+58
-15
lines changed

4 files changed

+58
-15
lines changed

plain-dev/plain/dev/poncho/color.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from collections.abc import Iterator
22

3-
ANSI_COLOURS = ["grey", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
3+
ANSI_COLOURS = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
44

55
for i, name in enumerate(ANSI_COLOURS):
66
globals()[name] = str(30 + i)
@@ -13,13 +13,11 @@ def get_colors() -> Iterator[str]:
1313
"yellow",
1414
"green",
1515
"magenta",
16-
"red",
1716
"blue",
1817
"intense_cyan",
1918
"intense_yellow",
2019
"intense_green",
2120
"intense_magenta",
22-
"intense_red",
2321
"intense_blue",
2422
]
2523
cs = [globals()[c] for c in cs]

plain-dev/plain/dev/poncho/manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ def _system_print(self, data: str) -> None:
217217
data=data,
218218
time=self._clock.now(),
219219
name=SYSTEM_PRINTER_NAME,
220-
color=None,
220+
color="2", # Dim prefix
221+
stream="stdout",
221222
)
222223
)

plain-dev/plain/dev/poncho/printer.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import re
2-
from collections import namedtuple
32
from pathlib import Path
4-
from typing import Any
3+
from typing import Any, NamedTuple
54

6-
Message = namedtuple("Message", "type data time name color")
5+
6+
class Message(NamedTuple):
7+
type: str
8+
data: bytes | dict[str, Any] | str
9+
time: Any
10+
name: str | None
11+
color: str | None
12+
stream: str = "stdout"
713

814

915
class Printer:
@@ -55,9 +61,25 @@ def write(self, message: Message) -> None:
5561
prefix = ""
5662
if self.prefix:
5763
time_formatted = message.time.strftime(self.time_format)
58-
prefix = f"{time_formatted} {name}| "
64+
prefix_base = f"{time_formatted} {name}"
65+
66+
# Color the timestamp and name with process color
5967
if self.color and message.color:
60-
prefix = _color_string(message.color, prefix)
68+
prefix_base = _color_string(message.color, prefix_base)
69+
70+
# Use fat red pipe for stderr, dim pipe for stdout
71+
if message.stream == "stderr" and self.color:
72+
pipe = _color_string("31", "┃")
73+
elif self.color:
74+
pipe = _color_string("2", "|")
75+
else:
76+
pipe = "|"
77+
78+
prefix = prefix_base + pipe + " "
79+
80+
# Dim the line content for system messages (color="2")
81+
if self.color and message.color == "2":
82+
line = _color_string("2", line)
6183

6284
formatted = prefix + line
6385

plain-dev/plain/dev/poncho/process.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import signal
44
import subprocess
5+
import threading
56
from queue import Queue
67
from typing import Any
78

@@ -50,15 +51,35 @@ def run(
5051
signal.signal(signal.SIGINT, signal.SIG_IGN)
5152
signal.signal(signal.SIGTERM, signal.SIG_IGN)
5253

53-
for line in iter(self._child.stdout.readline, b""):
54-
if not self.quiet:
55-
self._send_message(line)
56-
self._child.stdout.close()
54+
# Read stdout and stderr concurrently using threads
55+
stdout_thread = threading.Thread(
56+
target=self._read_stream, args=(self._child.stdout, "stdout")
57+
)
58+
stderr_thread = threading.Thread(
59+
target=self._read_stream, args=(self._child.stderr, "stderr")
60+
)
61+
62+
stdout_thread.start()
63+
stderr_thread.start()
64+
65+
# Wait for both threads to complete
66+
stdout_thread.join()
67+
stderr_thread.join()
68+
5769
self._child.wait()
5870

5971
self._send_message({"returncode": self._child.returncode}, type="stop")
6072

61-
def _send_message(self, data: bytes | dict[str, Any], type: str = "line") -> None:
73+
def _read_stream(self, stream: Any, stream_name: str) -> None:
74+
"""Read lines from a stream and send them as messages."""
75+
for line in iter(stream.readline, b""):
76+
if not self.quiet:
77+
self._send_message(line, stream=stream_name)
78+
stream.close()
79+
80+
def _send_message(
81+
self, data: bytes | dict[str, Any], type: str = "line", stream: str = "stdout"
82+
) -> None:
6283
if self._events is not None:
6384
self._events.put(
6485
Message(
@@ -67,6 +88,7 @@ def _send_message(self, data: bytes | dict[str, Any], type: str = "line") -> Non
6788
time=self._clock.now(),
6889
name=self.name,
6990
color=self.color,
91+
stream=stream,
7092
)
7193
)
7294

@@ -76,7 +98,7 @@ def __init__(self, cmd: str, **kwargs: Any) -> None:
7698
start_new_session = kwargs.pop("start_new_session", True)
7799
options = {
78100
"stdout": subprocess.PIPE,
79-
"stderr": subprocess.STDOUT,
101+
"stderr": subprocess.PIPE,
80102
"shell": True,
81103
"close_fds": not ON_WINDOWS,
82104
}

0 commit comments

Comments
 (0)