| 125 | import termios |
| 126 | |
| 127 | class Pty: |
| 128 | def __init__(self) -> None: |
| 129 | self.r: int | None = None |
| 130 | self.w: int | None = None |
| 131 | |
| 132 | def __enter__(self) -> Pty: |
| 133 | self.r, self.w = openpty() |
| 134 | |
| 135 | # tty flags normally change \n to \r\n |
| 136 | attrs = termios.tcgetattr(self.w) |
| 137 | assert isinstance(attrs[1], int) |
| 138 | attrs[1] &= ~(termios.ONLCR | termios.OPOST) |
| 139 | termios.tcsetattr(self.w, termios.TCSANOW, attrs) |
| 140 | |
| 141 | return self |
| 142 | |
| 143 | def close_w(self) -> None: |
| 144 | if self.w is not None: |
| 145 | os.close(self.w) |
| 146 | self.w = None |
| 147 | |
| 148 | def close_r(self) -> None: |
| 149 | assert self.r is not None |
| 150 | os.close(self.r) |
| 151 | self.r = None |
| 152 | |
| 153 | def __exit__( |
| 154 | self, |
| 155 | exc_type: type[BaseException] | None, |
| 156 | exc_value: BaseException | None, |
| 157 | traceback: TracebackType | None, |
| 158 | ) -> None: |
| 159 | self.close_w() |
| 160 | self.close_r() |
| 161 | |
| 162 | def cmd_output_p( |
| 163 | *cmd: str, |