+class StdoutInterceptor(io.TextIOBase):
+ def __init__(self):
+ self.saved_stdout: Optional[io.TextIOBase] = None
+ self.buf = ''
+
+ @abstractmethod
+ def write(self, s):
+ pass
+
+ def __enter__(self) -> None:
+ self.saved_stdout = sys.stdout
+ sys.stdout = self
+ return None
+
+ def __exit__(self, *args) -> bool:
+ sys.stdout = self.saved_stdout
+ print(self.buf)
+ return None
+
+
+class ProgrammableColorizer(StdoutInterceptor):
+ def __init__(self, patterns: Iterable[Tuple[re.Pattern, Callable[[Any, re.Pattern], str]]]):
+ super().__init__()
+ self.patterns = [_ for _ in patterns]
+
+ def write(self, s: str):
+ for pattern in self.patterns:
+ s = pattern[0].sub(pattern[1], s)
+ self.buf += s
+
+