from __future__ import annotations class Result: def __init__( self, stdout: bytes | None, stderr: bytes | None, status: int, encoding: str = 'UTF-8', strip: bool = True, cmd: list[str] | None = None, wd: str | None = None, ) -> None: self.__stdout = stdout self.__stderr = stderr self.__status = status self.__encoding = encoding self.__strip = strip self.__cmd = cmd self.__wd = wd def __decode(self, stdxxx: bytes | None) -> str | None: if stdxxx is None: return None ret = stdxxx.decode(self.encoding) if self.strip: return ret.strip() return ret @property def status(self) -> int | None: return self.__status @property def encoding(self) -> str: return self.__encoding @encoding.setter def encoding(self, value: str) -> None: self.__encoding = value def __stdout_footprint(self, quote = False) -> str: if self.__stdout is None: ret = '' else: ret = self.stdout_str[:20] if quote: ret = f'"{ret}"' return ret def __repr__(self) -> str: ret = f'{self.__status}:' if self.status != 0: ret += f' err: {self.stderr_str_or_none}' else: ret += f' out: {self.__stdout_footprint(quote=True)}' return ret @property def strip(self) -> bool: return self.__strip @strip.setter def strip(self, value: bool) -> None: self.__strip = value @property def cmd(self) -> list[str] | None: return self.__cmd @cmd.setter def cmd(self, value: list[str]) -> None: self.__cmd = value @property def wd(self) -> str | None: return self.__wd @wd.setter def wd(self, value: str) -> None: self.__wd = value def matches_error(self, pattern: str) -> bool: if self.status == 0: return False err = self.stderr_str if err is None: return False import re return re.search(pattern, err) is not None def __summarize(self, cmd: list[str] | None, wd: str | None = None) -> str: from .util import pretty_cmd if cmd is None: cmd = self.__cmd call = '' if cmd is not None: if wd is None: wd = self.__wd call = f'"{pretty_cmd(cmd, wd)}" ' ret = f'Command {call}has exited with status {self.__status}' call = pretty_cmd(cmd, wd) if self.status != 0: ret += f' -> stderr="{self.__stderr!r}"' else: if self.__stdout: ret += f' -> stdout has {len(self.__stdout)} bytes' else: ret += ' -> stdout = None' return ret def summarize(self, cmd: list[str] | None = None, wd: str | None = None) -> str: return self.__summarize(cmd, wd) @property def summary(self) -> str: return self.__summarize(None, None) @property def stdout(self) -> bytes: if self.__stdout is None: raise Exception(f'Result has no standard output stream: {self.summary}') return self.__stdout @property def stdout_or_none(self) -> bytes | None: return self.__stdout @property def stdout_str_or_none(self) -> str | None: return self.__decode(self.__stdout) @property def stdout_str(self) -> str: return self.stdout.decode(self.__encoding) @property def stderr(self) -> bytes: if self.__stderr is None: raise Exception(f'Result has no standard error stream: {self.summary}') return self.__stderr @property def stderr_or_none(self) -> bytes | None: return self.__stderr @property def stderr_str_or_none(self) -> str | None: return self.__decode(self.__stderr) @property def stderr_str(self) -> str: return self.stderr.decode(self.__encoding)