from__future__importannotations"""Composable *Rich*-native colouring pipeline==========================================Provides :class:`HighlighterPipeline`, an orchestrator that feeds rawmulti-line strings through a chain of :class:`LineHighlighter` stages.Each stage operates on :class:`rich.text.Text` so it can add style spanswithout mutating the text itself.Typical usage------------->>> pipeline = HighlighterPipeline([MySyntaxHL(), MyDiffHL()])>>> ansi_output = pipeline.colorize_and_render(src_string)print(ansi_output)"""fromtypingimportTYPE_CHECKING,Iterablefromrich.consoleimportConsolefromrich.textimportTextifTYPE_CHECKING:# pragma: no coverfrom.abstractionimportLineHighlighter# noqa: F401 (imported for typing only)
[docs]classHighlighterPipeline:# noqa: D101"""Chain of :pyclass:`LineHighlighter` stages. Parameters ---------- stages : Iterable of :class:`LineHighlighter` instances. The iterable is immediately materialised into a list so the pipeline can be reused. Note ---- * Each input line is passed through **every** stage in order. * If a stage exposes a bulk :pyfunc:`colorize_lines` method it is preferred over per-line iteration for performance. """def__init__(self,stages:Iterable["LineHighlighter"]):
# ------------------------------------------------------------------# Public helpers# ------------------------------------------------------------------
[docs]defcolorize(self,text:str)->Text:# noqa: D401"""Return a rich ``Text`` object with all styles applied. Parameters ---------- text : Multi-line string to be colourised. Returns ------- One composite `Text` built by joining all styled lines with ``\\n`` separators. """lines=text.splitlines()rich_lines=[Text(line)forlineinlines]forstageinself.stages:colorize_lines=getattr(stage,"colorize_lines",None)ifcallable(colorize_lines):colorize_lines(rich_lines)else:forrlinrich_lines:stage.colorize_line(rl)returnText("\n").join(rich_lines)
[docs]defcolorize_and_render(self,text:str)->str:"""Colourise and immediately render to ANSI. Parameters ---------- text : Multi-line input string. Returns ------- ANSI-encoded string ready for terminal output. """rich_lines=self.colorize(text)console=Console(force_terminal=True,color_system="truecolor",width=self._detect_width(),legacy_windows=False,)withconsole.capture()ascap:console.print(rich_lines,end="")returncap.get()
# ------------------------------------------------------------------# Internal helpers# ------------------------------------------------------------------@staticmethoddef_detect_width(default:int=512)->int:# noqa: D401"""Best-effort terminal width detection. Falls back to *default* when a real TTY is not present (e.g. in CI). Parameters ---------- default : Width to use when detection fails. Defaults to ``512``. Returns ------- Column count deemed safe for rendering. """try:fromshutilimportget_terminal_sizereturnmax(get_terminal_size().columns,20)exceptException:# pragma: no coverreturndefault