Thread-safe output capture to both file and console. Captures subprocess output (stdout + stderr merged) to a file while optionally echoing to console. Designed for capturing compilation errors. Example: tee = StreamTee(Path("build.log"), echo=True) tee.write_line(
| 12 | |
| 13 | |
| 14 | class StreamTee: |
| 15 | """ |
| 16 | Thread-safe output capture to both file and console. |
| 17 | |
| 18 | Captures subprocess output (stdout + stderr merged) to a file while |
| 19 | optionally echoing to console. Designed for capturing compilation errors. |
| 20 | |
| 21 | Example: |
| 22 | tee = StreamTee(Path("build.log"), echo=True) |
| 23 | tee.write_line("Compiling main.cpp...") |
| 24 | tee.write_line("Error: undefined reference to `foo`") |
| 25 | tee.write_footer(1) # Exit code 1 |
| 26 | tee.close() |
| 27 | """ |
| 28 | |
| 29 | def __init__(self, file_path: Path, echo: bool = True): |
| 30 | """ |
| 31 | Initialize StreamTee. |
| 32 | |
| 33 | Args: |
| 34 | file_path: Path to write captured output |
| 35 | echo: Whether to echo lines to console (default True) |
| 36 | """ |
| 37 | self._file_path = file_path |
| 38 | self._echo = echo |
| 39 | self._lock = threading.Lock() |
| 40 | |
| 41 | # Ensure parent directory exists |
| 42 | file_path.parent.mkdir(parents=True, exist_ok=True) |
| 43 | |
| 44 | # Open file with buffered I/O for performance |
| 45 | self._file_handle = open( |
| 46 | file_path, |
| 47 | "w", |
| 48 | encoding="utf-8", |
| 49 | buffering=8192, # 8KB buffer |
| 50 | ) |
| 51 | |
| 52 | def write_line(self, line: str) -> None: |
| 53 | """ |
| 54 | Write a line to both file and (optionally) console. |
| 55 | |
| 56 | Args: |
| 57 | line: Line to write (newline will be appended) |
| 58 | """ |
| 59 | with self._lock: |
| 60 | # Write to file |
| 61 | self._file_handle.write(line + "\n") |
| 62 | |
| 63 | # Echo to console if enabled |
| 64 | if self._echo: |
| 65 | print(line) |
| 66 | |
| 67 | def write_footer(self, exit_code: int) -> None: |
| 68 | """ |
| 69 | Write standardized footer with exit code. |
| 70 | |
| 71 | Format: |
no outgoing calls
no test coverage detected