]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_vendor/rich/console.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _vendor / rich / console.py
1 import inspect
2 import os
3 import platform
4 import sys
5 import threading
6 import zlib
7 from abc import ABC, abstractmethod
8 from dataclasses import dataclass, field
9 from datetime import datetime
10 from functools import wraps
11 from getpass import getpass
12 from html import escape
13 from inspect import isclass
14 from itertools import islice
15 from math import ceil
16 from time import monotonic
17 from types import FrameType, ModuleType, TracebackType
18 from typing import (
19 IO,
20 TYPE_CHECKING,
21 Any,
22 Callable,
23 Dict,
24 Iterable,
25 List,
26 Mapping,
27 NamedTuple,
28 Optional,
29 TextIO,
30 Tuple,
31 Type,
32 Union,
33 cast,
34 )
35
36 from pip._vendor.rich._null_file import NULL_FILE
37
38 if sys.version_info >= (3, 8):
39 from typing import Literal, Protocol, runtime_checkable
40 else:
41 from pip._vendor.typing_extensions import (
42 Literal,
43 Protocol,
44 runtime_checkable,
45 ) # pragma: no cover
46
47 from . import errors, themes
48 from ._emoji_replace import _emoji_replace
49 from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT
50 from ._fileno import get_fileno
51 from ._log_render import FormatTimeCallable, LogRender
52 from .align import Align, AlignMethod
53 from .color import ColorSystem, blend_rgb
54 from .control import Control
55 from .emoji import EmojiVariant
56 from .highlighter import NullHighlighter, ReprHighlighter
57 from .markup import render as render_markup
58 from .measure import Measurement, measure_renderables
59 from .pager import Pager, SystemPager
60 from .pretty import Pretty, is_expandable
61 from .protocol import rich_cast
62 from .region import Region
63 from .scope import render_scope
64 from .screen import Screen
65 from .segment import Segment
66 from .style import Style, StyleType
67 from .styled import Styled
68 from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme
69 from .text import Text, TextType
70 from .theme import Theme, ThemeStack
71
72 if TYPE_CHECKING:
73 from ._windows import WindowsConsoleFeatures
74 from .live import Live
75 from .status import Status
76
77 JUPYTER_DEFAULT_COLUMNS = 115
78 JUPYTER_DEFAULT_LINES = 100
79 WINDOWS = platform.system() == "Windows"
80
81 HighlighterType = Callable[[Union[str, "Text"]], "Text"]
82 JustifyMethod = Literal["default", "left", "center", "right", "full"]
83 OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"]
84
85
86 class NoChange:
87 pass
88
89
90 NO_CHANGE = NoChange()
91
92 try:
93 _STDIN_FILENO = sys.__stdin__.fileno()
94 except Exception:
95 _STDIN_FILENO = 0
96 try:
97 _STDOUT_FILENO = sys.__stdout__.fileno()
98 except Exception:
99 _STDOUT_FILENO = 1
100 try:
101 _STDERR_FILENO = sys.__stderr__.fileno()
102 except Exception:
103 _STDERR_FILENO = 2
104
105 _STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO)
106 _STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO)
107
108
109 _TERM_COLORS = {
110 "kitty": ColorSystem.EIGHT_BIT,
111 "256color": ColorSystem.EIGHT_BIT,
112 "16color": ColorSystem.STANDARD,
113 }
114
115
116 class ConsoleDimensions(NamedTuple):
117 """Size of the terminal."""
118
119 width: int
120 """The width of the console in 'cells'."""
121 height: int
122 """The height of the console in lines."""
123
124
125 @dataclass
126 class ConsoleOptions:
127 """Options for __rich_console__ method."""
128
129 size: ConsoleDimensions
130 """Size of console."""
131 legacy_windows: bool
132 """legacy_windows: flag for legacy windows."""
133 min_width: int
134 """Minimum width of renderable."""
135 max_width: int
136 """Maximum width of renderable."""
137 is_terminal: bool
138 """True if the target is a terminal, otherwise False."""
139 encoding: str
140 """Encoding of terminal."""
141 max_height: int
142 """Height of container (starts as terminal)"""
143 justify: Optional[JustifyMethod] = None
144 """Justify value override for renderable."""
145 overflow: Optional[OverflowMethod] = None
146 """Overflow value override for renderable."""
147 no_wrap: Optional[bool] = False
148 """Disable wrapping for text."""
149 highlight: Optional[bool] = None
150 """Highlight override for render_str."""
151 markup: Optional[bool] = None
152 """Enable markup when rendering strings."""
153 height: Optional[int] = None
154
155 @property
156 def ascii_only(self) -> bool:
157 """Check if renderables should use ascii only."""
158 return not self.encoding.startswith("utf")
159
160 def copy(self) -> "ConsoleOptions":
161 """Return a copy of the options.
162
163 Returns:
164 ConsoleOptions: a copy of self.
165 """
166 options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions)
167 options.__dict__ = self.__dict__.copy()
168 return options
169
170 def update(
171 self,
172 *,
173 width: Union[int, NoChange] = NO_CHANGE,
174 min_width: Union[int, NoChange] = NO_CHANGE,
175 max_width: Union[int, NoChange] = NO_CHANGE,
176 justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE,
177 overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE,
178 no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE,
179 highlight: Union[Optional[bool], NoChange] = NO_CHANGE,
180 markup: Union[Optional[bool], NoChange] = NO_CHANGE,
181 height: Union[Optional[int], NoChange] = NO_CHANGE,
182 ) -> "ConsoleOptions":
183 """Update values, return a copy."""
184 options = self.copy()
185 if not isinstance(width, NoChange):
186 options.min_width = options.max_width = max(0, width)
187 if not isinstance(min_width, NoChange):
188 options.min_width = min_width
189 if not isinstance(max_width, NoChange):
190 options.max_width = max_width
191 if not isinstance(justify, NoChange):
192 options.justify = justify
193 if not isinstance(overflow, NoChange):
194 options.overflow = overflow
195 if not isinstance(no_wrap, NoChange):
196 options.no_wrap = no_wrap
197 if not isinstance(highlight, NoChange):
198 options.highlight = highlight
199 if not isinstance(markup, NoChange):
200 options.markup = markup
201 if not isinstance(height, NoChange):
202 if height is not None:
203 options.max_height = height
204 options.height = None if height is None else max(0, height)
205 return options
206
207 def update_width(self, width: int) -> "ConsoleOptions":
208 """Update just the width, return a copy.
209
210 Args:
211 width (int): New width (sets both min_width and max_width)
212
213 Returns:
214 ~ConsoleOptions: New console options instance.
215 """
216 options = self.copy()
217 options.min_width = options.max_width = max(0, width)
218 return options
219
220 def update_height(self, height: int) -> "ConsoleOptions":
221 """Update the height, and return a copy.
222
223 Args:
224 height (int): New height
225
226 Returns:
227 ~ConsoleOptions: New Console options instance.
228 """
229 options = self.copy()
230 options.max_height = options.height = height
231 return options
232
233 def reset_height(self) -> "ConsoleOptions":
234 """Return a copy of the options with height set to ``None``.
235
236 Returns:
237 ~ConsoleOptions: New console options instance.
238 """
239 options = self.copy()
240 options.height = None
241 return options
242
243 def update_dimensions(self, width: int, height: int) -> "ConsoleOptions":
244 """Update the width and height, and return a copy.
245
246 Args:
247 width (int): New width (sets both min_width and max_width).
248 height (int): New height.
249
250 Returns:
251 ~ConsoleOptions: New console options instance.
252 """
253 options = self.copy()
254 options.min_width = options.max_width = max(0, width)
255 options.height = options.max_height = height
256 return options
257
258
259 @runtime_checkable
260 class RichCast(Protocol):
261 """An object that may be 'cast' to a console renderable."""
262
263 def __rich__(
264 self,
265 ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover
266 ...
267
268
269 @runtime_checkable
270 class ConsoleRenderable(Protocol):
271 """An object that supports the console protocol."""
272
273 def __rich_console__(
274 self, console: "Console", options: "ConsoleOptions"
275 ) -> "RenderResult": # pragma: no cover
276 ...
277
278
279 # A type that may be rendered by Console.
280 RenderableType = Union[ConsoleRenderable, RichCast, str]
281
282 # The result of calling a __rich_console__ method.
283 RenderResult = Iterable[Union[RenderableType, Segment]]
284
285 _null_highlighter = NullHighlighter()
286
287
288 class CaptureError(Exception):
289 """An error in the Capture context manager."""
290
291
292 class NewLine:
293 """A renderable to generate new line(s)"""
294
295 def __init__(self, count: int = 1) -> None:
296 self.count = count
297
298 def __rich_console__(
299 self, console: "Console", options: "ConsoleOptions"
300 ) -> Iterable[Segment]:
301 yield Segment("\n" * self.count)
302
303
304 class ScreenUpdate:
305 """Render a list of lines at a given offset."""
306
307 def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None:
308 self._lines = lines
309 self.x = x
310 self.y = y
311
312 def __rich_console__(
313 self, console: "Console", options: ConsoleOptions
314 ) -> RenderResult:
315 x = self.x
316 move_to = Control.move_to
317 for offset, line in enumerate(self._lines, self.y):
318 yield move_to(x, offset)
319 yield from line
320
321
322 class Capture:
323 """Context manager to capture the result of printing to the console.
324 See :meth:`~rich.console.Console.capture` for how to use.
325
326 Args:
327 console (Console): A console instance to capture output.
328 """
329
330 def __init__(self, console: "Console") -> None:
331 self._console = console
332 self._result: Optional[str] = None
333
334 def __enter__(self) -> "Capture":
335 self._console.begin_capture()
336 return self
337
338 def __exit__(
339 self,
340 exc_type: Optional[Type[BaseException]],
341 exc_val: Optional[BaseException],
342 exc_tb: Optional[TracebackType],
343 ) -> None:
344 self._result = self._console.end_capture()
345
346 def get(self) -> str:
347 """Get the result of the capture."""
348 if self._result is None:
349 raise CaptureError(
350 "Capture result is not available until context manager exits."
351 )
352 return self._result
353
354
355 class ThemeContext:
356 """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage."""
357
358 def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None:
359 self.console = console
360 self.theme = theme
361 self.inherit = inherit
362
363 def __enter__(self) -> "ThemeContext":
364 self.console.push_theme(self.theme)
365 return self
366
367 def __exit__(
368 self,
369 exc_type: Optional[Type[BaseException]],
370 exc_val: Optional[BaseException],
371 exc_tb: Optional[TracebackType],
372 ) -> None:
373 self.console.pop_theme()
374
375
376 class PagerContext:
377 """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage."""
378
379 def __init__(
380 self,
381 console: "Console",
382 pager: Optional[Pager] = None,
383 styles: bool = False,
384 links: bool = False,
385 ) -> None:
386 self._console = console
387 self.pager = SystemPager() if pager is None else pager
388 self.styles = styles
389 self.links = links
390
391 def __enter__(self) -> "PagerContext":
392 self._console._enter_buffer()
393 return self
394
395 def __exit__(
396 self,
397 exc_type: Optional[Type[BaseException]],
398 exc_val: Optional[BaseException],
399 exc_tb: Optional[TracebackType],
400 ) -> None:
401 if exc_type is None:
402 with self._console._lock:
403 buffer: List[Segment] = self._console._buffer[:]
404 del self._console._buffer[:]
405 segments: Iterable[Segment] = buffer
406 if not self.styles:
407 segments = Segment.strip_styles(segments)
408 elif not self.links:
409 segments = Segment.strip_links(segments)
410 content = self._console._render_buffer(segments)
411 self.pager.show(content)
412 self._console._exit_buffer()
413
414
415 class ScreenContext:
416 """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage."""
417
418 def __init__(
419 self, console: "Console", hide_cursor: bool, style: StyleType = ""
420 ) -> None:
421 self.console = console
422 self.hide_cursor = hide_cursor
423 self.screen = Screen(style=style)
424 self._changed = False
425
426 def update(
427 self, *renderables: RenderableType, style: Optional[StyleType] = None
428 ) -> None:
429 """Update the screen.
430
431 Args:
432 renderable (RenderableType, optional): Optional renderable to replace current renderable,
433 or None for no change. Defaults to None.
434 style: (Style, optional): Replacement style, or None for no change. Defaults to None.
435 """
436 if renderables:
437 self.screen.renderable = (
438 Group(*renderables) if len(renderables) > 1 else renderables[0]
439 )
440 if style is not None:
441 self.screen.style = style
442 self.console.print(self.screen, end="")
443
444 def __enter__(self) -> "ScreenContext":
445 self._changed = self.console.set_alt_screen(True)
446 if self._changed and self.hide_cursor:
447 self.console.show_cursor(False)
448 return self
449
450 def __exit__(
451 self,
452 exc_type: Optional[Type[BaseException]],
453 exc_val: Optional[BaseException],
454 exc_tb: Optional[TracebackType],
455 ) -> None:
456 if self._changed:
457 self.console.set_alt_screen(False)
458 if self.hide_cursor:
459 self.console.show_cursor(True)
460
461
462 class Group:
463 """Takes a group of renderables and returns a renderable object that renders the group.
464
465 Args:
466 renderables (Iterable[RenderableType]): An iterable of renderable objects.
467 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.
468 """
469
470 def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None:
471 self._renderables = renderables
472 self.fit = fit
473 self._render: Optional[List[RenderableType]] = None
474
475 @property
476 def renderables(self) -> List["RenderableType"]:
477 if self._render is None:
478 self._render = list(self._renderables)
479 return self._render
480
481 def __rich_measure__(
482 self, console: "Console", options: "ConsoleOptions"
483 ) -> "Measurement":
484 if self.fit:
485 return measure_renderables(console, options, self.renderables)
486 else:
487 return Measurement(options.max_width, options.max_width)
488
489 def __rich_console__(
490 self, console: "Console", options: "ConsoleOptions"
491 ) -> RenderResult:
492 yield from self.renderables
493
494
495 def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:
496 """A decorator that turns an iterable of renderables in to a group.
497
498 Args:
499 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.
500 """
501
502 def decorator(
503 method: Callable[..., Iterable[RenderableType]]
504 ) -> Callable[..., Group]:
505 """Convert a method that returns an iterable of renderables in to a Group."""
506
507 @wraps(method)
508 def _replace(*args: Any, **kwargs: Any) -> Group:
509 renderables = method(*args, **kwargs)
510 return Group(*renderables, fit=fit)
511
512 return _replace
513
514 return decorator
515
516
517 def _is_jupyter() -> bool: # pragma: no cover
518 """Check if we're running in a Jupyter notebook."""
519 try:
520 get_ipython # type: ignore[name-defined]
521 except NameError:
522 return False
523 ipython = get_ipython() # type: ignore[name-defined]
524 shell = ipython.__class__.__name__
525 if (
526 "google.colab" in str(ipython.__class__)
527 or os.getenv("DATABRICKS_RUNTIME_VERSION")
528 or shell == "ZMQInteractiveShell"
529 ):
530 return True # Jupyter notebook or qtconsole
531 elif shell == "TerminalInteractiveShell":
532 return False # Terminal running IPython
533 else:
534 return False # Other type (?)
535
536
537 COLOR_SYSTEMS = {
538 "standard": ColorSystem.STANDARD,
539 "256": ColorSystem.EIGHT_BIT,
540 "truecolor": ColorSystem.TRUECOLOR,
541 "windows": ColorSystem.WINDOWS,
542 }
543
544 _COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()}
545
546
547 @dataclass
548 class ConsoleThreadLocals(threading.local):
549 """Thread local values for Console context."""
550
551 theme_stack: ThemeStack
552 buffer: List[Segment] = field(default_factory=list)
553 buffer_index: int = 0
554
555
556 class RenderHook(ABC):
557 """Provides hooks in to the render process."""
558
559 @abstractmethod
560 def process_renderables(
561 self, renderables: List[ConsoleRenderable]
562 ) -> List[ConsoleRenderable]:
563 """Called with a list of objects to render.
564
565 This method can return a new list of renderables, or modify and return the same list.
566
567 Args:
568 renderables (List[ConsoleRenderable]): A number of renderable objects.
569
570 Returns:
571 List[ConsoleRenderable]: A replacement list of renderables.
572 """
573
574
575 _windows_console_features: Optional["WindowsConsoleFeatures"] = None
576
577
578 def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover
579 global _windows_console_features
580 if _windows_console_features is not None:
581 return _windows_console_features
582 from ._windows import get_windows_console_features
583
584 _windows_console_features = get_windows_console_features()
585 return _windows_console_features
586
587
588 def detect_legacy_windows() -> bool:
589 """Detect legacy Windows."""
590 return WINDOWS and not get_windows_console_features().vt
591
592
593 class Console:
594 """A high level console interface.
595
596 Args:
597 color_system (str, optional): The color system supported by your terminal,
598 either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect.
599 force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None.
600 force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None.
601 force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None.
602 soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False.
603 theme (Theme, optional): An optional style theme object, or ``None`` for default theme.
604 stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False.
605 file (IO, optional): A file object where the console should write to. Defaults to stdout.
606 quiet (bool, Optional): Boolean to suppress all output. Defaults to False.
607 width (int, optional): The width of the terminal. Leave as default to auto-detect width.
608 height (int, optional): The height of the terminal. Leave as default to auto-detect height.
609 style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None.
610 no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None.
611 tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8.
612 record (bool, optional): Boolean to enable recording of terminal output,
613 required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False.
614 markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True.
615 emoji (bool, optional): Enable emoji code. Defaults to True.
616 emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None.
617 highlight (bool, optional): Enable automatic highlighting. Defaults to True.
618 log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True.
619 log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True.
620 log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ".
621 highlighter (HighlighterType, optional): Default highlighter.
622 legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``.
623 safe_box (bool, optional): Restrict box options that don't render on legacy Windows.
624 get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log),
625 or None for datetime.now.
626 get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic.
627 """
628
629 _environ: Mapping[str, str] = os.environ
630
631 def __init__(
632 self,
633 *,
634 color_system: Optional[
635 Literal["auto", "standard", "256", "truecolor", "windows"]
636 ] = "auto",
637 force_terminal: Optional[bool] = None,
638 force_jupyter: Optional[bool] = None,
639 force_interactive: Optional[bool] = None,
640 soft_wrap: bool = False,
641 theme: Optional[Theme] = None,
642 stderr: bool = False,
643 file: Optional[IO[str]] = None,
644 quiet: bool = False,
645 width: Optional[int] = None,
646 height: Optional[int] = None,
647 style: Optional[StyleType] = None,
648 no_color: Optional[bool] = None,
649 tab_size: int = 8,
650 record: bool = False,
651 markup: bool = True,
652 emoji: bool = True,
653 emoji_variant: Optional[EmojiVariant] = None,
654 highlight: bool = True,
655 log_time: bool = True,
656 log_path: bool = True,
657 log_time_format: Union[str, FormatTimeCallable] = "[%X]",
658 highlighter: Optional["HighlighterType"] = ReprHighlighter(),
659 legacy_windows: Optional[bool] = None,
660 safe_box: bool = True,
661 get_datetime: Optional[Callable[[], datetime]] = None,
662 get_time: Optional[Callable[[], float]] = None,
663 _environ: Optional[Mapping[str, str]] = None,
664 ):
665 # Copy of os.environ allows us to replace it for testing
666 if _environ is not None:
667 self._environ = _environ
668
669 self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter
670 if self.is_jupyter:
671 if width is None:
672 jupyter_columns = self._environ.get("JUPYTER_COLUMNS")
673 if jupyter_columns is not None and jupyter_columns.isdigit():
674 width = int(jupyter_columns)
675 else:
676 width = JUPYTER_DEFAULT_COLUMNS
677 if height is None:
678 jupyter_lines = self._environ.get("JUPYTER_LINES")
679 if jupyter_lines is not None and jupyter_lines.isdigit():
680 height = int(jupyter_lines)
681 else:
682 height = JUPYTER_DEFAULT_LINES
683
684 self.tab_size = tab_size
685 self.record = record
686 self._markup = markup
687 self._emoji = emoji
688 self._emoji_variant: Optional[EmojiVariant] = emoji_variant
689 self._highlight = highlight
690 self.legacy_windows: bool = (
691 (detect_legacy_windows() and not self.is_jupyter)
692 if legacy_windows is None
693 else legacy_windows
694 )
695
696 if width is None:
697 columns = self._environ.get("COLUMNS")
698 if columns is not None and columns.isdigit():
699 width = int(columns) - self.legacy_windows
700 if height is None:
701 lines = self._environ.get("LINES")
702 if lines is not None and lines.isdigit():
703 height = int(lines)
704
705 self.soft_wrap = soft_wrap
706 self._width = width
707 self._height = height
708
709 self._color_system: Optional[ColorSystem]
710
711 self._force_terminal = None
712 if force_terminal is not None:
713 self._force_terminal = force_terminal
714
715 self._file = file
716 self.quiet = quiet
717 self.stderr = stderr
718
719 if color_system is None:
720 self._color_system = None
721 elif color_system == "auto":
722 self._color_system = self._detect_color_system()
723 else:
724 self._color_system = COLOR_SYSTEMS[color_system]
725
726 self._lock = threading.RLock()
727 self._log_render = LogRender(
728 show_time=log_time,
729 show_path=log_path,
730 time_format=log_time_format,
731 )
732 self.highlighter: HighlighterType = highlighter or _null_highlighter
733 self.safe_box = safe_box
734 self.get_datetime = get_datetime or datetime.now
735 self.get_time = get_time or monotonic
736 self.style = style
737 self.no_color = (
738 no_color if no_color is not None else "NO_COLOR" in self._environ
739 )
740 self.is_interactive = (
741 (self.is_terminal and not self.is_dumb_terminal)
742 if force_interactive is None
743 else force_interactive
744 )
745
746 self._record_buffer_lock = threading.RLock()
747 self._thread_locals = ConsoleThreadLocals(
748 theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme)
749 )
750 self._record_buffer: List[Segment] = []
751 self._render_hooks: List[RenderHook] = []
752 self._live: Optional["Live"] = None
753 self._is_alt_screen = False
754
755 def __repr__(self) -> str:
756 return f"<console width={self.width} {self._color_system!s}>"
757
758 @property
759 def file(self) -> IO[str]:
760 """Get the file object to write to."""
761 file = self._file or (sys.stderr if self.stderr else sys.stdout)
762 file = getattr(file, "rich_proxied_file", file)
763 if file is None:
764 file = NULL_FILE
765 return file
766
767 @file.setter
768 def file(self, new_file: IO[str]) -> None:
769 """Set a new file object."""
770 self._file = new_file
771
772 @property
773 def _buffer(self) -> List[Segment]:
774 """Get a thread local buffer."""
775 return self._thread_locals.buffer
776
777 @property
778 def _buffer_index(self) -> int:
779 """Get a thread local buffer."""
780 return self._thread_locals.buffer_index
781
782 @_buffer_index.setter
783 def _buffer_index(self, value: int) -> None:
784 self._thread_locals.buffer_index = value
785
786 @property
787 def _theme_stack(self) -> ThemeStack:
788 """Get the thread local theme stack."""
789 return self._thread_locals.theme_stack
790
791 def _detect_color_system(self) -> Optional[ColorSystem]:
792 """Detect color system from env vars."""
793 if self.is_jupyter:
794 return ColorSystem.TRUECOLOR
795 if not self.is_terminal or self.is_dumb_terminal:
796 return None
797 if WINDOWS: # pragma: no cover
798 if self.legacy_windows: # pragma: no cover
799 return ColorSystem.WINDOWS
800 windows_console_features = get_windows_console_features()
801 return (
802 ColorSystem.TRUECOLOR
803 if windows_console_features.truecolor
804 else ColorSystem.EIGHT_BIT
805 )
806 else:
807 color_term = self._environ.get("COLORTERM", "").strip().lower()
808 if color_term in ("truecolor", "24bit"):
809 return ColorSystem.TRUECOLOR
810 term = self._environ.get("TERM", "").strip().lower()
811 _term_name, _hyphen, colors = term.rpartition("-")
812 color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD)
813 return color_system
814
815 def _enter_buffer(self) -> None:
816 """Enter in to a buffer context, and buffer all output."""
817 self._buffer_index += 1
818
819 def _exit_buffer(self) -> None:
820 """Leave buffer context, and render content if required."""
821 self._buffer_index -= 1
822 self._check_buffer()
823
824 def set_live(self, live: "Live") -> None:
825 """Set Live instance. Used by Live context manager.
826
827 Args:
828 live (Live): Live instance using this Console.
829
830 Raises:
831 errors.LiveError: If this Console has a Live context currently active.
832 """
833 with self._lock:
834 if self._live is not None:
835 raise errors.LiveError("Only one live display may be active at once")
836 self._live = live
837
838 def clear_live(self) -> None:
839 """Clear the Live instance."""
840 with self._lock:
841 self._live = None
842
843 def push_render_hook(self, hook: RenderHook) -> None:
844 """Add a new render hook to the stack.
845
846 Args:
847 hook (RenderHook): Render hook instance.
848 """
849 with self._lock:
850 self._render_hooks.append(hook)
851
852 def pop_render_hook(self) -> None:
853 """Pop the last renderhook from the stack."""
854 with self._lock:
855 self._render_hooks.pop()
856
857 def __enter__(self) -> "Console":
858 """Own context manager to enter buffer context."""
859 self._enter_buffer()
860 return self
861
862 def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
863 """Exit buffer context."""
864 self._exit_buffer()
865
866 def begin_capture(self) -> None:
867 """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output."""
868 self._enter_buffer()
869
870 def end_capture(self) -> str:
871 """End capture mode and return captured string.
872
873 Returns:
874 str: Console output.
875 """
876 render_result = self._render_buffer(self._buffer)
877 del self._buffer[:]
878 self._exit_buffer()
879 return render_result
880
881 def push_theme(self, theme: Theme, *, inherit: bool = True) -> None:
882 """Push a new theme on to the top of the stack, replacing the styles from the previous theme.
883 Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather
884 than calling this method directly.
885
886 Args:
887 theme (Theme): A theme instance.
888 inherit (bool, optional): Inherit existing styles. Defaults to True.
889 """
890 self._theme_stack.push_theme(theme, inherit=inherit)
891
892 def pop_theme(self) -> None:
893 """Remove theme from top of stack, restoring previous theme."""
894 self._theme_stack.pop_theme()
895
896 def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext:
897 """Use a different theme for the duration of the context manager.
898
899 Args:
900 theme (Theme): Theme instance to user.
901 inherit (bool, optional): Inherit existing console styles. Defaults to True.
902
903 Returns:
904 ThemeContext: [description]
905 """
906 return ThemeContext(self, theme, inherit)
907
908 @property
909 def color_system(self) -> Optional[str]:
910 """Get color system string.
911
912 Returns:
913 Optional[str]: "standard", "256" or "truecolor".
914 """
915
916 if self._color_system is not None:
917 return _COLOR_SYSTEMS_NAMES[self._color_system]
918 else:
919 return None
920
921 @property
922 def encoding(self) -> str:
923 """Get the encoding of the console file, e.g. ``"utf-8"``.
924
925 Returns:
926 str: A standard encoding string.
927 """
928 return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower()
929
930 @property
931 def is_terminal(self) -> bool:
932 """Check if the console is writing to a terminal.
933
934 Returns:
935 bool: True if the console writing to a device capable of
936 understanding terminal codes, otherwise False.
937 """
938 if self._force_terminal is not None:
939 return self._force_terminal
940
941 if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith(
942 "idlelib"
943 ):
944 # Return False for Idle which claims to be a tty but can't handle ansi codes
945 return False
946
947 if self.is_jupyter:
948 # return False for Jupyter, which may have FORCE_COLOR set
949 return False
950
951 # If FORCE_COLOR env var has any value at all, we assume a terminal.
952 force_color = self._environ.get("FORCE_COLOR")
953 if force_color is not None:
954 self._force_terminal = True
955 return True
956
957 isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None)
958 try:
959 return False if isatty is None else isatty()
960 except ValueError:
961 # in some situation (at the end of a pytest run for example) isatty() can raise
962 # ValueError: I/O operation on closed file
963 # return False because we aren't in a terminal anymore
964 return False
965
966 @property
967 def is_dumb_terminal(self) -> bool:
968 """Detect dumb terminal.
969
970 Returns:
971 bool: True if writing to a dumb terminal, otherwise False.
972
973 """
974 _term = self._environ.get("TERM", "")
975 is_dumb = _term.lower() in ("dumb", "unknown")
976 return self.is_terminal and is_dumb
977
978 @property
979 def options(self) -> ConsoleOptions:
980 """Get default console options."""
981 return ConsoleOptions(
982 max_height=self.size.height,
983 size=self.size,
984 legacy_windows=self.legacy_windows,
985 min_width=1,
986 max_width=self.width,
987 encoding=self.encoding,
988 is_terminal=self.is_terminal,
989 )
990
991 @property
992 def size(self) -> ConsoleDimensions:
993 """Get the size of the console.
994
995 Returns:
996 ConsoleDimensions: A named tuple containing the dimensions.
997 """
998
999 if self._width is not None and self._height is not None:
1000 return ConsoleDimensions(self._width - self.legacy_windows, self._height)
1001
1002 if self.is_dumb_terminal:
1003 return ConsoleDimensions(80, 25)
1004
1005 width: Optional[int] = None
1006 height: Optional[int] = None
1007
1008 if WINDOWS: # pragma: no cover
1009 try:
1010 width, height = os.get_terminal_size()
1011 except (AttributeError, ValueError, OSError): # Probably not a terminal
1012 pass
1013 else:
1014 for file_descriptor in _STD_STREAMS:
1015 try:
1016 width, height = os.get_terminal_size(file_descriptor)
1017 except (AttributeError, ValueError, OSError):
1018 pass
1019 else:
1020 break
1021
1022 columns = self._environ.get("COLUMNS")
1023 if columns is not None and columns.isdigit():
1024 width = int(columns)
1025 lines = self._environ.get("LINES")
1026 if lines is not None and lines.isdigit():
1027 height = int(lines)
1028
1029 # get_terminal_size can report 0, 0 if run from pseudo-terminal
1030 width = width or 80
1031 height = height or 25
1032 return ConsoleDimensions(
1033 width - self.legacy_windows if self._width is None else self._width,
1034 height if self._height is None else self._height,
1035 )
1036
1037 @size.setter
1038 def size(self, new_size: Tuple[int, int]) -> None:
1039 """Set a new size for the terminal.
1040
1041 Args:
1042 new_size (Tuple[int, int]): New width and height.
1043 """
1044 width, height = new_size
1045 self._width = width
1046 self._height = height
1047
1048 @property
1049 def width(self) -> int:
1050 """Get the width of the console.
1051
1052 Returns:
1053 int: The width (in characters) of the console.
1054 """
1055 return self.size.width
1056
1057 @width.setter
1058 def width(self, width: int) -> None:
1059 """Set width.
1060
1061 Args:
1062 width (int): New width.
1063 """
1064 self._width = width
1065
1066 @property
1067 def height(self) -> int:
1068 """Get the height of the console.
1069
1070 Returns:
1071 int: The height (in lines) of the console.
1072 """
1073 return self.size.height
1074
1075 @height.setter
1076 def height(self, height: int) -> None:
1077 """Set height.
1078
1079 Args:
1080 height (int): new height.
1081 """
1082 self._height = height
1083
1084 def bell(self) -> None:
1085 """Play a 'bell' sound (if supported by the terminal)."""
1086 self.control(Control.bell())
1087
1088 def capture(self) -> Capture:
1089 """A context manager to *capture* the result of print() or log() in a string,
1090 rather than writing it to the console.
1091
1092 Example:
1093 >>> from rich.console import Console
1094 >>> console = Console()
1095 >>> with console.capture() as capture:
1096 ... console.print("[bold magenta]Hello World[/]")
1097 >>> print(capture.get())
1098
1099 Returns:
1100 Capture: Context manager with disables writing to the terminal.
1101 """
1102 capture = Capture(self)
1103 return capture
1104
1105 def pager(
1106 self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False
1107 ) -> PagerContext:
1108 """A context manager to display anything printed within a "pager". The pager application
1109 is defined by the system and will typically support at least pressing a key to scroll.
1110
1111 Args:
1112 pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None.
1113 styles (bool, optional): Show styles in pager. Defaults to False.
1114 links (bool, optional): Show links in pager. Defaults to False.
1115
1116 Example:
1117 >>> from rich.console import Console
1118 >>> from rich.__main__ import make_test_card
1119 >>> console = Console()
1120 >>> with console.pager():
1121 console.print(make_test_card())
1122
1123 Returns:
1124 PagerContext: A context manager.
1125 """
1126 return PagerContext(self, pager=pager, styles=styles, links=links)
1127
1128 def line(self, count: int = 1) -> None:
1129 """Write new line(s).
1130
1131 Args:
1132 count (int, optional): Number of new lines. Defaults to 1.
1133 """
1134
1135 assert count >= 0, "count must be >= 0"
1136 self.print(NewLine(count))
1137
1138 def clear(self, home: bool = True) -> None:
1139 """Clear the screen.
1140
1141 Args:
1142 home (bool, optional): Also move the cursor to 'home' position. Defaults to True.
1143 """
1144 if home:
1145 self.control(Control.clear(), Control.home())
1146 else:
1147 self.control(Control.clear())
1148
1149 def status(
1150 self,
1151 status: RenderableType,
1152 *,
1153 spinner: str = "dots",
1154 spinner_style: StyleType = "status.spinner",
1155 speed: float = 1.0,
1156 refresh_per_second: float = 12.5,
1157 ) -> "Status":
1158 """Display a status and spinner.
1159
1160 Args:
1161 status (RenderableType): A status renderable (str or Text typically).
1162 spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".
1163 spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".
1164 speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.
1165 refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.
1166
1167 Returns:
1168 Status: A Status object that may be used as a context manager.
1169 """
1170 from .status import Status
1171
1172 status_renderable = Status(
1173 status,
1174 console=self,
1175 spinner=spinner,
1176 spinner_style=spinner_style,
1177 speed=speed,
1178 refresh_per_second=refresh_per_second,
1179 )
1180 return status_renderable
1181
1182 def show_cursor(self, show: bool = True) -> bool:
1183 """Show or hide the cursor.
1184
1185 Args:
1186 show (bool, optional): Set visibility of the cursor.
1187 """
1188 if self.is_terminal:
1189 self.control(Control.show_cursor(show))
1190 return True
1191 return False
1192
1193 def set_alt_screen(self, enable: bool = True) -> bool:
1194 """Enables alternative screen mode.
1195
1196 Note, if you enable this mode, you should ensure that is disabled before
1197 the application exits. See :meth:`~rich.Console.screen` for a context manager
1198 that handles this for you.
1199
1200 Args:
1201 enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True.
1202
1203 Returns:
1204 bool: True if the control codes were written.
1205
1206 """
1207 changed = False
1208 if self.is_terminal and not self.legacy_windows:
1209 self.control(Control.alt_screen(enable))
1210 changed = True
1211 self._is_alt_screen = enable
1212 return changed
1213
1214 @property
1215 def is_alt_screen(self) -> bool:
1216 """Check if the alt screen was enabled.
1217
1218 Returns:
1219 bool: True if the alt screen was enabled, otherwise False.
1220 """
1221 return self._is_alt_screen
1222
1223 def set_window_title(self, title: str) -> bool:
1224 """Set the title of the console terminal window.
1225
1226 Warning: There is no means within Rich of "resetting" the window title to its
1227 previous value, meaning the title you set will persist even after your application
1228 exits.
1229
1230 ``fish`` shell resets the window title before and after each command by default,
1231 negating this issue. Windows Terminal and command prompt will also reset the title for you.
1232 Most other shells and terminals, however, do not do this.
1233
1234 Some terminals may require configuration changes before you can set the title.
1235 Some terminals may not support setting the title at all.
1236
1237 Other software (including the terminal itself, the shell, custom prompts, plugins, etc.)
1238 may also set the terminal window title. This could result in whatever value you write
1239 using this method being overwritten.
1240
1241 Args:
1242 title (str): The new title of the terminal window.
1243
1244 Returns:
1245 bool: True if the control code to change the terminal title was
1246 written, otherwise False. Note that a return value of True
1247 does not guarantee that the window title has actually changed,
1248 since the feature may be unsupported/disabled in some terminals.
1249 """
1250 if self.is_terminal:
1251 self.control(Control.title(title))
1252 return True
1253 return False
1254
1255 def screen(
1256 self, hide_cursor: bool = True, style: Optional[StyleType] = None
1257 ) -> "ScreenContext":
1258 """Context manager to enable and disable 'alternative screen' mode.
1259
1260 Args:
1261 hide_cursor (bool, optional): Also hide the cursor. Defaults to False.
1262 style (Style, optional): Optional style for screen. Defaults to None.
1263
1264 Returns:
1265 ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit.
1266 """
1267 return ScreenContext(self, hide_cursor=hide_cursor, style=style or "")
1268
1269 def measure(
1270 self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None
1271 ) -> Measurement:
1272 """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains
1273 information regarding the number of characters required to print the renderable.
1274
1275 Args:
1276 renderable (RenderableType): Any renderable or string.
1277 options (Optional[ConsoleOptions], optional): Options to use when measuring, or None
1278 to use default options. Defaults to None.
1279
1280 Returns:
1281 Measurement: A measurement of the renderable.
1282 """
1283 measurement = Measurement.get(self, options or self.options, renderable)
1284 return measurement
1285
1286 def render(
1287 self, renderable: RenderableType, options: Optional[ConsoleOptions] = None
1288 ) -> Iterable[Segment]:
1289 """Render an object in to an iterable of `Segment` instances.
1290
1291 This method contains the logic for rendering objects with the console protocol.
1292 You are unlikely to need to use it directly, unless you are extending the library.
1293
1294 Args:
1295 renderable (RenderableType): An object supporting the console protocol, or
1296 an object that may be converted to a string.
1297 options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None.
1298
1299 Returns:
1300 Iterable[Segment]: An iterable of segments that may be rendered.
1301 """
1302
1303 _options = options or self.options
1304 if _options.max_width < 1:
1305 # No space to render anything. This prevents potential recursion errors.
1306 return
1307 render_iterable: RenderResult
1308
1309 renderable = rich_cast(renderable)
1310 if hasattr(renderable, "__rich_console__") and not isclass(renderable):
1311 render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr]
1312 elif isinstance(renderable, str):
1313 text_renderable = self.render_str(
1314 renderable, highlight=_options.highlight, markup=_options.markup
1315 )
1316 render_iterable = text_renderable.__rich_console__(self, _options)
1317 else:
1318 raise errors.NotRenderableError(
1319 f"Unable to render {renderable!r}; "
1320 "A str, Segment or object with __rich_console__ method is required"
1321 )
1322
1323 try:
1324 iter_render = iter(render_iterable)
1325 except TypeError:
1326 raise errors.NotRenderableError(
1327 f"object {render_iterable!r} is not renderable"
1328 )
1329 _Segment = Segment
1330 _options = _options.reset_height()
1331 for render_output in iter_render:
1332 if isinstance(render_output, _Segment):
1333 yield render_output
1334 else:
1335 yield from self.render(render_output, _options)
1336
1337 def render_lines(
1338 self,
1339 renderable: RenderableType,
1340 options: Optional[ConsoleOptions] = None,
1341 *,
1342 style: Optional[Style] = None,
1343 pad: bool = True,
1344 new_lines: bool = False,
1345 ) -> List[List[Segment]]:
1346 """Render objects in to a list of lines.
1347
1348 The output of render_lines is useful when further formatting of rendered console text
1349 is required, such as the Panel class which draws a border around any renderable object.
1350
1351 Args:
1352 renderable (RenderableType): Any object renderable in the console.
1353 options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``.
1354 style (Style, optional): Optional style to apply to renderables. Defaults to ``None``.
1355 pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``.
1356 new_lines (bool, optional): Include "\n" characters at end of lines.
1357
1358 Returns:
1359 List[List[Segment]]: A list of lines, where a line is a list of Segment objects.
1360 """
1361 with self._lock:
1362 render_options = options or self.options
1363 _rendered = self.render(renderable, render_options)
1364 if style:
1365 _rendered = Segment.apply_style(_rendered, style)
1366
1367 render_height = render_options.height
1368 if render_height is not None:
1369 render_height = max(0, render_height)
1370
1371 lines = list(
1372 islice(
1373 Segment.split_and_crop_lines(
1374 _rendered,
1375 render_options.max_width,
1376 include_new_lines=new_lines,
1377 pad=pad,
1378 style=style,
1379 ),
1380 None,
1381 render_height,
1382 )
1383 )
1384 if render_options.height is not None:
1385 extra_lines = render_options.height - len(lines)
1386 if extra_lines > 0:
1387 pad_line = [
1388 [Segment(" " * render_options.max_width, style), Segment("\n")]
1389 if new_lines
1390 else [Segment(" " * render_options.max_width, style)]
1391 ]
1392 lines.extend(pad_line * extra_lines)
1393
1394 return lines
1395
1396 def render_str(
1397 self,
1398 text: str,
1399 *,
1400 style: Union[str, Style] = "",
1401 justify: Optional[JustifyMethod] = None,
1402 overflow: Optional[OverflowMethod] = None,
1403 emoji: Optional[bool] = None,
1404 markup: Optional[bool] = None,
1405 highlight: Optional[bool] = None,
1406 highlighter: Optional[HighlighterType] = None,
1407 ) -> "Text":
1408 """Convert a string to a Text instance. This is called automatically if
1409 you print or log a string.
1410
1411 Args:
1412 text (str): Text to render.
1413 style (Union[str, Style], optional): Style to apply to rendered text.
1414 justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``.
1415 overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``.
1416 emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default.
1417 markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default.
1418 highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default.
1419 highlighter (HighlighterType, optional): Optional highlighter to apply.
1420 Returns:
1421 ConsoleRenderable: Renderable object.
1422
1423 """
1424 emoji_enabled = emoji or (emoji is None and self._emoji)
1425 markup_enabled = markup or (markup is None and self._markup)
1426 highlight_enabled = highlight or (highlight is None and self._highlight)
1427
1428 if markup_enabled:
1429 rich_text = render_markup(
1430 text,
1431 style=style,
1432 emoji=emoji_enabled,
1433 emoji_variant=self._emoji_variant,
1434 )
1435 rich_text.justify = justify
1436 rich_text.overflow = overflow
1437 else:
1438 rich_text = Text(
1439 _emoji_replace(text, default_variant=self._emoji_variant)
1440 if emoji_enabled
1441 else text,
1442 justify=justify,
1443 overflow=overflow,
1444 style=style,
1445 )
1446
1447 _highlighter = (highlighter or self.highlighter) if highlight_enabled else None
1448 if _highlighter is not None:
1449 highlight_text = _highlighter(str(rich_text))
1450 highlight_text.copy_styles(rich_text)
1451 return highlight_text
1452
1453 return rich_text
1454
1455 def get_style(
1456 self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None
1457 ) -> Style:
1458 """Get a Style instance by its theme name or parse a definition.
1459
1460 Args:
1461 name (str): The name of a style or a style definition.
1462
1463 Returns:
1464 Style: A Style object.
1465
1466 Raises:
1467 MissingStyle: If no style could be parsed from name.
1468
1469 """
1470 if isinstance(name, Style):
1471 return name
1472
1473 try:
1474 style = self._theme_stack.get(name)
1475 if style is None:
1476 style = Style.parse(name)
1477 return style.copy() if style.link else style
1478 except errors.StyleSyntaxError as error:
1479 if default is not None:
1480 return self.get_style(default)
1481 raise errors.MissingStyle(
1482 f"Failed to get style {name!r}; {error}"
1483 ) from None
1484
1485 def _collect_renderables(
1486 self,
1487 objects: Iterable[Any],
1488 sep: str,
1489 end: str,
1490 *,
1491 justify: Optional[JustifyMethod] = None,
1492 emoji: Optional[bool] = None,
1493 markup: Optional[bool] = None,
1494 highlight: Optional[bool] = None,
1495 ) -> List[ConsoleRenderable]:
1496 """Combine a number of renderables and text into one renderable.
1497
1498 Args:
1499 objects (Iterable[Any]): Anything that Rich can render.
1500 sep (str): String to write between print data.
1501 end (str): String to write at end of print data.
1502 justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
1503 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default.
1504 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default.
1505 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default.
1506
1507 Returns:
1508 List[ConsoleRenderable]: A list of things to render.
1509 """
1510 renderables: List[ConsoleRenderable] = []
1511 _append = renderables.append
1512 text: List[Text] = []
1513 append_text = text.append
1514
1515 append = _append
1516 if justify in ("left", "center", "right"):
1517
1518 def align_append(renderable: RenderableType) -> None:
1519 _append(Align(renderable, cast(AlignMethod, justify)))
1520
1521 append = align_append
1522
1523 _highlighter: HighlighterType = _null_highlighter
1524 if highlight or (highlight is None and self._highlight):
1525 _highlighter = self.highlighter
1526
1527 def check_text() -> None:
1528 if text:
1529 sep_text = Text(sep, justify=justify, end=end)
1530 append(sep_text.join(text))
1531 text.clear()
1532
1533 for renderable in objects:
1534 renderable = rich_cast(renderable)
1535 if isinstance(renderable, str):
1536 append_text(
1537 self.render_str(
1538 renderable, emoji=emoji, markup=markup, highlighter=_highlighter
1539 )
1540 )
1541 elif isinstance(renderable, Text):
1542 append_text(renderable)
1543 elif isinstance(renderable, ConsoleRenderable):
1544 check_text()
1545 append(renderable)
1546 elif is_expandable(renderable):
1547 check_text()
1548 append(Pretty(renderable, highlighter=_highlighter))
1549 else:
1550 append_text(_highlighter(str(renderable)))
1551
1552 check_text()
1553
1554 if self.style is not None:
1555 style = self.get_style(self.style)
1556 renderables = [Styled(renderable, style) for renderable in renderables]
1557
1558 return renderables
1559
1560 def rule(
1561 self,
1562 title: TextType = "",
1563 *,
1564 characters: str = "─",
1565 style: Union[str, Style] = "rule.line",
1566 align: AlignMethod = "center",
1567 ) -> None:
1568 """Draw a line with optional centered title.
1569
1570 Args:
1571 title (str, optional): Text to render over the rule. Defaults to "".
1572 characters (str, optional): Character(s) to form the line. Defaults to "─".
1573 style (str, optional): Style of line. Defaults to "rule.line".
1574 align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center".
1575 """
1576 from .rule import Rule
1577
1578 rule = Rule(title=title, characters=characters, style=style, align=align)
1579 self.print(rule)
1580
1581 def control(self, *control: Control) -> None:
1582 """Insert non-printing control codes.
1583
1584 Args:
1585 control_codes (str): Control codes, such as those that may move the cursor.
1586 """
1587 if not self.is_dumb_terminal:
1588 with self:
1589 self._buffer.extend(_control.segment for _control in control)
1590
1591 def out(
1592 self,
1593 *objects: Any,
1594 sep: str = " ",
1595 end: str = "\n",
1596 style: Optional[Union[str, Style]] = None,
1597 highlight: Optional[bool] = None,
1598 ) -> None:
1599 """Output to the terminal. This is a low-level way of writing to the terminal which unlike
1600 :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will
1601 optionally apply highlighting and a basic style.
1602
1603 Args:
1604 sep (str, optional): String to write between print data. Defaults to " ".
1605 end (str, optional): String to write at end of print data. Defaults to "\\\\n".
1606 style (Union[str, Style], optional): A style to apply to output. Defaults to None.
1607 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use
1608 console default. Defaults to ``None``.
1609 """
1610 raw_output: str = sep.join(str(_object) for _object in objects)
1611 self.print(
1612 raw_output,
1613 style=style,
1614 highlight=highlight,
1615 emoji=False,
1616 markup=False,
1617 no_wrap=True,
1618 overflow="ignore",
1619 crop=False,
1620 end=end,
1621 )
1622
1623 def print(
1624 self,
1625 *objects: Any,
1626 sep: str = " ",
1627 end: str = "\n",
1628 style: Optional[Union[str, Style]] = None,
1629 justify: Optional[JustifyMethod] = None,
1630 overflow: Optional[OverflowMethod] = None,
1631 no_wrap: Optional[bool] = None,
1632 emoji: Optional[bool] = None,
1633 markup: Optional[bool] = None,
1634 highlight: Optional[bool] = None,
1635 width: Optional[int] = None,
1636 height: Optional[int] = None,
1637 crop: bool = True,
1638 soft_wrap: Optional[bool] = None,
1639 new_line_start: bool = False,
1640 ) -> None:
1641 """Print to the console.
1642
1643 Args:
1644 objects (positional args): Objects to log to the terminal.
1645 sep (str, optional): String to write between print data. Defaults to " ".
1646 end (str, optional): String to write at end of print data. Defaults to "\\\\n".
1647 style (Union[str, Style], optional): A style to apply to output. Defaults to None.
1648 justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``.
1649 overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None.
1650 no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None.
1651 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``.
1652 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``.
1653 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``.
1654 width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``.
1655 crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True.
1656 soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for
1657 Console default. Defaults to ``None``.
1658 new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``.
1659 """
1660 if not objects:
1661 objects = (NewLine(),)
1662
1663 if soft_wrap is None:
1664 soft_wrap = self.soft_wrap
1665 if soft_wrap:
1666 if no_wrap is None:
1667 no_wrap = True
1668 if overflow is None:
1669 overflow = "ignore"
1670 crop = False
1671 render_hooks = self._render_hooks[:]
1672 with self:
1673 renderables = self._collect_renderables(
1674 objects,
1675 sep,
1676 end,
1677 justify=justify,
1678 emoji=emoji,
1679 markup=markup,
1680 highlight=highlight,
1681 )
1682 for hook in render_hooks:
1683 renderables = hook.process_renderables(renderables)
1684 render_options = self.options.update(
1685 justify=justify,
1686 overflow=overflow,
1687 width=min(width, self.width) if width is not None else NO_CHANGE,
1688 height=height,
1689 no_wrap=no_wrap,
1690 markup=markup,
1691 highlight=highlight,
1692 )
1693
1694 new_segments: List[Segment] = []
1695 extend = new_segments.extend
1696 render = self.render
1697 if style is None:
1698 for renderable in renderables:
1699 extend(render(renderable, render_options))
1700 else:
1701 for renderable in renderables:
1702 extend(
1703 Segment.apply_style(
1704 render(renderable, render_options), self.get_style(style)
1705 )
1706 )
1707 if new_line_start:
1708 if (
1709 len("".join(segment.text for segment in new_segments).splitlines())
1710 > 1
1711 ):
1712 new_segments.insert(0, Segment.line())
1713 if crop:
1714 buffer_extend = self._buffer.extend
1715 for line in Segment.split_and_crop_lines(
1716 new_segments, self.width, pad=False
1717 ):
1718 buffer_extend(line)
1719 else:
1720 self._buffer.extend(new_segments)
1721
1722 def print_json(
1723 self,
1724 json: Optional[str] = None,
1725 *,
1726 data: Any = None,
1727 indent: Union[None, int, str] = 2,
1728 highlight: bool = True,
1729 skip_keys: bool = False,
1730 ensure_ascii: bool = False,
1731 check_circular: bool = True,
1732 allow_nan: bool = True,
1733 default: Optional[Callable[[Any], Any]] = None,
1734 sort_keys: bool = False,
1735 ) -> None:
1736 """Pretty prints JSON. Output will be valid JSON.
1737
1738 Args:
1739 json (Optional[str]): A string containing JSON.
1740 data (Any): If json is not supplied, then encode this data.
1741 indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2.
1742 highlight (bool, optional): Enable highlighting of output: Defaults to True.
1743 skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
1744 ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
1745 check_circular (bool, optional): Check for circular references. Defaults to True.
1746 allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
1747 default (Callable, optional): A callable that converts values that can not be encoded
1748 in to something that can be JSON encoded. Defaults to None.
1749 sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
1750 """
1751 from pip._vendor.rich.json import JSON
1752
1753 if json is None:
1754 json_renderable = JSON.from_data(
1755 data,
1756 indent=indent,
1757 highlight=highlight,
1758 skip_keys=skip_keys,
1759 ensure_ascii=ensure_ascii,
1760 check_circular=check_circular,
1761 allow_nan=allow_nan,
1762 default=default,
1763 sort_keys=sort_keys,
1764 )
1765 else:
1766 if not isinstance(json, str):
1767 raise TypeError(
1768 f"json must be str. Did you mean print_json(data={json!r}) ?"
1769 )
1770 json_renderable = JSON(
1771 json,
1772 indent=indent,
1773 highlight=highlight,
1774 skip_keys=skip_keys,
1775 ensure_ascii=ensure_ascii,
1776 check_circular=check_circular,
1777 allow_nan=allow_nan,
1778 default=default,
1779 sort_keys=sort_keys,
1780 )
1781 self.print(json_renderable, soft_wrap=True)
1782
1783 def update_screen(
1784 self,
1785 renderable: RenderableType,
1786 *,
1787 region: Optional[Region] = None,
1788 options: Optional[ConsoleOptions] = None,
1789 ) -> None:
1790 """Update the screen at a given offset.
1791
1792 Args:
1793 renderable (RenderableType): A Rich renderable.
1794 region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None.
1795 x (int, optional): x offset. Defaults to 0.
1796 y (int, optional): y offset. Defaults to 0.
1797
1798 Raises:
1799 errors.NoAltScreen: If the Console isn't in alt screen mode.
1800
1801 """
1802 if not self.is_alt_screen:
1803 raise errors.NoAltScreen("Alt screen must be enabled to call update_screen")
1804 render_options = options or self.options
1805 if region is None:
1806 x = y = 0
1807 render_options = render_options.update_dimensions(
1808 render_options.max_width, render_options.height or self.height
1809 )
1810 else:
1811 x, y, width, height = region
1812 render_options = render_options.update_dimensions(width, height)
1813
1814 lines = self.render_lines(renderable, options=render_options)
1815 self.update_screen_lines(lines, x, y)
1816
1817 def update_screen_lines(
1818 self, lines: List[List[Segment]], x: int = 0, y: int = 0
1819 ) -> None:
1820 """Update lines of the screen at a given offset.
1821
1822 Args:
1823 lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`).
1824 x (int, optional): x offset (column no). Defaults to 0.
1825 y (int, optional): y offset (column no). Defaults to 0.
1826
1827 Raises:
1828 errors.NoAltScreen: If the Console isn't in alt screen mode.
1829 """
1830 if not self.is_alt_screen:
1831 raise errors.NoAltScreen("Alt screen must be enabled to call update_screen")
1832 screen_update = ScreenUpdate(lines, x, y)
1833 segments = self.render(screen_update)
1834 self._buffer.extend(segments)
1835 self._check_buffer()
1836
1837 def print_exception(
1838 self,
1839 *,
1840 width: Optional[int] = 100,
1841 extra_lines: int = 3,
1842 theme: Optional[str] = None,
1843 word_wrap: bool = False,
1844 show_locals: bool = False,
1845 suppress: Iterable[Union[str, ModuleType]] = (),
1846 max_frames: int = 100,
1847 ) -> None:
1848 """Prints a rich render of the last exception and traceback.
1849
1850 Args:
1851 width (Optional[int], optional): Number of characters used to render code. Defaults to 100.
1852 extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
1853 theme (str, optional): Override pygments theme used in traceback
1854 word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
1855 show_locals (bool, optional): Enable display of local variables. Defaults to False.
1856 suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
1857 max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
1858 """
1859 from .traceback import Traceback
1860
1861 traceback = Traceback(
1862 width=width,
1863 extra_lines=extra_lines,
1864 theme=theme,
1865 word_wrap=word_wrap,
1866 show_locals=show_locals,
1867 suppress=suppress,
1868 max_frames=max_frames,
1869 )
1870 self.print(traceback)
1871
1872 @staticmethod
1873 def _caller_frame_info(
1874 offset: int,
1875 currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe,
1876 ) -> Tuple[str, int, Dict[str, Any]]:
1877 """Get caller frame information.
1878
1879 Args:
1880 offset (int): the caller offset within the current frame stack.
1881 currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to
1882 retrieve the current frame. Defaults to ``inspect.currentframe``.
1883
1884 Returns:
1885 Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and
1886 the dictionary of local variables associated with the caller frame.
1887
1888 Raises:
1889 RuntimeError: If the stack offset is invalid.
1890 """
1891 # Ignore the frame of this local helper
1892 offset += 1
1893
1894 frame = currentframe()
1895 if frame is not None:
1896 # Use the faster currentframe where implemented
1897 while offset and frame is not None:
1898 frame = frame.f_back
1899 offset -= 1
1900 assert frame is not None
1901 return frame.f_code.co_filename, frame.f_lineno, frame.f_locals
1902 else:
1903 # Fallback to the slower stack
1904 frame_info = inspect.stack()[offset]
1905 return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals
1906
1907 def log(
1908 self,
1909 *objects: Any,
1910 sep: str = " ",
1911 end: str = "\n",
1912 style: Optional[Union[str, Style]] = None,
1913 justify: Optional[JustifyMethod] = None,
1914 emoji: Optional[bool] = None,
1915 markup: Optional[bool] = None,
1916 highlight: Optional[bool] = None,
1917 log_locals: bool = False,
1918 _stack_offset: int = 1,
1919 ) -> None:
1920 """Log rich content to the terminal.
1921
1922 Args:
1923 objects (positional args): Objects to log to the terminal.
1924 sep (str, optional): String to write between print data. Defaults to " ".
1925 end (str, optional): String to write at end of print data. Defaults to "\\\\n".
1926 style (Union[str, Style], optional): A style to apply to output. Defaults to None.
1927 justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
1928 overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None.
1929 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None.
1930 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None.
1931 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None.
1932 log_locals (bool, optional): Boolean to enable logging of locals where ``log()``
1933 was called. Defaults to False.
1934 _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1.
1935 """
1936 if not objects:
1937 objects = (NewLine(),)
1938
1939 render_hooks = self._render_hooks[:]
1940
1941 with self:
1942 renderables = self._collect_renderables(
1943 objects,
1944 sep,
1945 end,
1946 justify=justify,
1947 emoji=emoji,
1948 markup=markup,
1949 highlight=highlight,
1950 )
1951 if style is not None:
1952 renderables = [Styled(renderable, style) for renderable in renderables]
1953
1954 filename, line_no, locals = self._caller_frame_info(_stack_offset)
1955 link_path = None if filename.startswith("<") else os.path.abspath(filename)
1956 path = filename.rpartition(os.sep)[-1]
1957 if log_locals:
1958 locals_map = {
1959 key: value
1960 for key, value in locals.items()
1961 if not key.startswith("__")
1962 }
1963 renderables.append(render_scope(locals_map, title="[i]locals"))
1964
1965 renderables = [
1966 self._log_render(
1967 self,
1968 renderables,
1969 log_time=self.get_datetime(),
1970 path=path,
1971 line_no=line_no,
1972 link_path=link_path,
1973 )
1974 ]
1975 for hook in render_hooks:
1976 renderables = hook.process_renderables(renderables)
1977 new_segments: List[Segment] = []
1978 extend = new_segments.extend
1979 render = self.render
1980 render_options = self.options
1981 for renderable in renderables:
1982 extend(render(renderable, render_options))
1983 buffer_extend = self._buffer.extend
1984 for line in Segment.split_and_crop_lines(
1985 new_segments, self.width, pad=False
1986 ):
1987 buffer_extend(line)
1988
1989 def _check_buffer(self) -> None:
1990 """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False)
1991 Rendering is supported on Windows, Unix and Jupyter environments. For
1992 legacy Windows consoles, the win32 API is called directly.
1993 This method will also record what it renders if recording is enabled via Console.record.
1994 """
1995 if self.quiet:
1996 del self._buffer[:]
1997 return
1998 with self._lock:
1999 if self.record:
2000 with self._record_buffer_lock:
2001 self._record_buffer.extend(self._buffer[:])
2002
2003 if self._buffer_index == 0:
2004 if self.is_jupyter: # pragma: no cover
2005 from .jupyter import display
2006
2007 display(self._buffer, self._render_buffer(self._buffer[:]))
2008 del self._buffer[:]
2009 else:
2010 if WINDOWS:
2011 use_legacy_windows_render = False
2012 if self.legacy_windows:
2013 fileno = get_fileno(self.file)
2014 if fileno is not None:
2015 use_legacy_windows_render = (
2016 fileno in _STD_STREAMS_OUTPUT
2017 )
2018
2019 if use_legacy_windows_render:
2020 from pip._vendor.rich._win32_console import LegacyWindowsTerm
2021 from pip._vendor.rich._windows_renderer import legacy_windows_render
2022
2023 buffer = self._buffer[:]
2024 if self.no_color and self._color_system:
2025 buffer = list(Segment.remove_color(buffer))
2026
2027 legacy_windows_render(buffer, LegacyWindowsTerm(self.file))
2028 else:
2029 # Either a non-std stream on legacy Windows, or modern Windows.
2030 text = self._render_buffer(self._buffer[:])
2031 # https://bugs.python.org/issue37871
2032 # https://github.com/python/cpython/issues/82052
2033 # We need to avoid writing more than 32Kb in a single write, due to the above bug
2034 write = self.file.write
2035 # Worse case scenario, every character is 4 bytes of utf-8
2036 MAX_WRITE = 32 * 1024 // 4
2037 try:
2038 if len(text) <= MAX_WRITE:
2039 write(text)
2040 else:
2041 batch: List[str] = []
2042 batch_append = batch.append
2043 size = 0
2044 for line in text.splitlines(True):
2045 if size + len(line) > MAX_WRITE and batch:
2046 write("".join(batch))
2047 batch.clear()
2048 size = 0
2049 batch_append(line)
2050 size += len(line)
2051 if batch:
2052 write("".join(batch))
2053 batch.clear()
2054 except UnicodeEncodeError as error:
2055 error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
2056 raise
2057 else:
2058 text = self._render_buffer(self._buffer[:])
2059 try:
2060 self.file.write(text)
2061 except UnicodeEncodeError as error:
2062 error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
2063 raise
2064
2065 self.file.flush()
2066 del self._buffer[:]
2067
2068 def _render_buffer(self, buffer: Iterable[Segment]) -> str:
2069 """Render buffered output, and clear buffer."""
2070 output: List[str] = []
2071 append = output.append
2072 color_system = self._color_system
2073 legacy_windows = self.legacy_windows
2074 not_terminal = not self.is_terminal
2075 if self.no_color and color_system:
2076 buffer = Segment.remove_color(buffer)
2077 for text, style, control in buffer:
2078 if style:
2079 append(
2080 style.render(
2081 text,
2082 color_system=color_system,
2083 legacy_windows=legacy_windows,
2084 )
2085 )
2086 elif not (not_terminal and control):
2087 append(text)
2088
2089 rendered = "".join(output)
2090 return rendered
2091
2092 def input(
2093 self,
2094 prompt: TextType = "",
2095 *,
2096 markup: bool = True,
2097 emoji: bool = True,
2098 password: bool = False,
2099 stream: Optional[TextIO] = None,
2100 ) -> str:
2101 """Displays a prompt and waits for input from the user. The prompt may contain color / style.
2102
2103 It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded.
2104
2105 Args:
2106 prompt (Union[str, Text]): Text to render in the prompt.
2107 markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True.
2108 emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True.
2109 password: (bool, optional): Hide typed text. Defaults to False.
2110 stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None.
2111
2112 Returns:
2113 str: Text read from stdin.
2114 """
2115 if prompt:
2116 self.print(prompt, markup=markup, emoji=emoji, end="")
2117 if password:
2118 result = getpass("", stream=stream)
2119 else:
2120 if stream:
2121 result = stream.readline()
2122 else:
2123 result = input()
2124 return result
2125
2126 def export_text(self, *, clear: bool = True, styles: bool = False) -> str:
2127 """Generate text from console contents (requires record=True argument in constructor).
2128
2129 Args:
2130 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
2131 styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text.
2132 Defaults to ``False``.
2133
2134 Returns:
2135 str: String containing console contents.
2136
2137 """
2138 assert (
2139 self.record
2140 ), "To export console contents set record=True in the constructor or instance"
2141
2142 with self._record_buffer_lock:
2143 if styles:
2144 text = "".join(
2145 (style.render(text) if style else text)
2146 for text, style, _ in self._record_buffer
2147 )
2148 else:
2149 text = "".join(
2150 segment.text
2151 for segment in self._record_buffer
2152 if not segment.control
2153 )
2154 if clear:
2155 del self._record_buffer[:]
2156 return text
2157
2158 def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None:
2159 """Generate text from console and save to a given location (requires record=True argument in constructor).
2160
2161 Args:
2162 path (str): Path to write text files.
2163 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
2164 styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text.
2165 Defaults to ``False``.
2166
2167 """
2168 text = self.export_text(clear=clear, styles=styles)
2169 with open(path, "wt", encoding="utf-8") as write_file:
2170 write_file.write(text)
2171
2172 def export_html(
2173 self,
2174 *,
2175 theme: Optional[TerminalTheme] = None,
2176 clear: bool = True,
2177 code_format: Optional[str] = None,
2178 inline_styles: bool = False,
2179 ) -> str:
2180 """Generate HTML from console contents (requires record=True argument in constructor).
2181
2182 Args:
2183 theme (TerminalTheme, optional): TerminalTheme object containing console colors.
2184 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
2185 code_format (str, optional): Format string to render HTML. In addition to '{foreground}',
2186 '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``.
2187 inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files
2188 larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag.
2189 Defaults to False.
2190
2191 Returns:
2192 str: String containing console contents as HTML.
2193 """
2194 assert (
2195 self.record
2196 ), "To export console contents set record=True in the constructor or instance"
2197 fragments: List[str] = []
2198 append = fragments.append
2199 _theme = theme or DEFAULT_TERMINAL_THEME
2200 stylesheet = ""
2201
2202 render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format
2203
2204 with self._record_buffer_lock:
2205 if inline_styles:
2206 for text, style, _ in Segment.filter_control(
2207 Segment.simplify(self._record_buffer)
2208 ):
2209 text = escape(text)
2210 if style:
2211 rule = style.get_html_style(_theme)
2212 if style.link:
2213 text = f'<a href="{style.link}">{text}</a>'
2214 text = f'<span style="{rule}">{text}</span>' if rule else text
2215 append(text)
2216 else:
2217 styles: Dict[str, int] = {}
2218 for text, style, _ in Segment.filter_control(
2219 Segment.simplify(self._record_buffer)
2220 ):
2221 text = escape(text)
2222 if style:
2223 rule = style.get_html_style(_theme)
2224 style_number = styles.setdefault(rule, len(styles) + 1)
2225 if style.link:
2226 text = f'<a class="r{style_number}" href="{style.link}">{text}</a>'
2227 else:
2228 text = f'<span class="r{style_number}">{text}</span>'
2229 append(text)
2230 stylesheet_rules: List[str] = []
2231 stylesheet_append = stylesheet_rules.append
2232 for style_rule, style_number in styles.items():
2233 if style_rule:
2234 stylesheet_append(f".r{style_number} {{{style_rule}}}")
2235 stylesheet = "\n".join(stylesheet_rules)
2236
2237 rendered_code = render_code_format.format(
2238 code="".join(fragments),
2239 stylesheet=stylesheet,
2240 foreground=_theme.foreground_color.hex,
2241 background=_theme.background_color.hex,
2242 )
2243 if clear:
2244 del self._record_buffer[:]
2245 return rendered_code
2246
2247 def save_html(
2248 self,
2249 path: str,
2250 *,
2251 theme: Optional[TerminalTheme] = None,
2252 clear: bool = True,
2253 code_format: str = CONSOLE_HTML_FORMAT,
2254 inline_styles: bool = False,
2255 ) -> None:
2256 """Generate HTML from console contents and write to a file (requires record=True argument in constructor).
2257
2258 Args:
2259 path (str): Path to write html file.
2260 theme (TerminalTheme, optional): TerminalTheme object containing console colors.
2261 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
2262 code_format (str, optional): Format string to render HTML. In addition to '{foreground}',
2263 '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``.
2264 inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files
2265 larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag.
2266 Defaults to False.
2267
2268 """
2269 html = self.export_html(
2270 theme=theme,
2271 clear=clear,
2272 code_format=code_format,
2273 inline_styles=inline_styles,
2274 )
2275 with open(path, "wt", encoding="utf-8") as write_file:
2276 write_file.write(html)
2277
2278 def export_svg(
2279 self,
2280 *,
2281 title: str = "Rich",
2282 theme: Optional[TerminalTheme] = None,
2283 clear: bool = True,
2284 code_format: str = CONSOLE_SVG_FORMAT,
2285 font_aspect_ratio: float = 0.61,
2286 unique_id: Optional[str] = None,
2287 ) -> str:
2288 """
2289 Generate an SVG from the console contents (requires record=True in Console constructor).
2290
2291 Args:
2292 title (str, optional): The title of the tab in the output image
2293 theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
2294 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
2295 code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
2296 into the string in order to form the final SVG output. The default template used and the variables
2297 injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
2298 font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
2299 string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
2300 If you aren't specifying a different font inside ``code_format``, you probably don't need this.
2301 unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
2302 ids). If not set, this defaults to a computed value based on the recorded content.
2303 """
2304
2305 from pip._vendor.rich.cells import cell_len
2306
2307 style_cache: Dict[Style, str] = {}
2308
2309 def get_svg_style(style: Style) -> str:
2310 """Convert a Style to CSS rules for SVG."""
2311 if style in style_cache:
2312 return style_cache[style]
2313 css_rules = []
2314 color = (
2315 _theme.foreground_color
2316 if (style.color is None or style.color.is_default)
2317 else style.color.get_truecolor(_theme)
2318 )
2319 bgcolor = (
2320 _theme.background_color
2321 if (style.bgcolor is None or style.bgcolor.is_default)
2322 else style.bgcolor.get_truecolor(_theme)
2323 )
2324 if style.reverse:
2325 color, bgcolor = bgcolor, color
2326 if style.dim:
2327 color = blend_rgb(color, bgcolor, 0.4)
2328 css_rules.append(f"fill: {color.hex}")
2329 if style.bold:
2330 css_rules.append("font-weight: bold")
2331 if style.italic:
2332 css_rules.append("font-style: italic;")
2333 if style.underline:
2334 css_rules.append("text-decoration: underline;")
2335 if style.strike:
2336 css_rules.append("text-decoration: line-through;")
2337
2338 css = ";".join(css_rules)
2339 style_cache[style] = css
2340 return css
2341
2342 _theme = theme or SVG_EXPORT_THEME
2343
2344 width = self.width
2345 char_height = 20
2346 char_width = char_height * font_aspect_ratio
2347 line_height = char_height * 1.22
2348
2349 margin_top = 1
2350 margin_right = 1
2351 margin_bottom = 1
2352 margin_left = 1
2353
2354 padding_top = 40
2355 padding_right = 8
2356 padding_bottom = 8
2357 padding_left = 8
2358
2359 padding_width = padding_left + padding_right
2360 padding_height = padding_top + padding_bottom
2361 margin_width = margin_left + margin_right
2362 margin_height = margin_top + margin_bottom
2363
2364 text_backgrounds: List[str] = []
2365 text_group: List[str] = []
2366 classes: Dict[str, int] = {}
2367 style_no = 1
2368
2369 def escape_text(text: str) -> str:
2370 """HTML escape text and replace spaces with nbsp."""
2371 return escape(text).replace(" ", "&#160;")
2372
2373 def make_tag(
2374 name: str, content: Optional[str] = None, **attribs: object
2375 ) -> str:
2376 """Make a tag from name, content, and attributes."""
2377
2378 def stringify(value: object) -> str:
2379 if isinstance(value, (float)):
2380 return format(value, "g")
2381 return str(value)
2382
2383 tag_attribs = " ".join(
2384 f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"'
2385 for k, v in attribs.items()
2386 )
2387 return (
2388 f"<{name} {tag_attribs}>{content}</{name}>"
2389 if content
2390 else f"<{name} {tag_attribs}/>"
2391 )
2392
2393 with self._record_buffer_lock:
2394 segments = list(Segment.filter_control(self._record_buffer))
2395 if clear:
2396 self._record_buffer.clear()
2397
2398 if unique_id is None:
2399 unique_id = "terminal-" + str(
2400 zlib.adler32(
2401 ("".join(repr(segment) for segment in segments)).encode(
2402 "utf-8",
2403 "ignore",
2404 )
2405 + title.encode("utf-8", "ignore")
2406 )
2407 )
2408 y = 0
2409 for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)):
2410 x = 0
2411 for text, style, _control in line:
2412 style = style or Style()
2413 rules = get_svg_style(style)
2414 if rules not in classes:
2415 classes[rules] = style_no
2416 style_no += 1
2417 class_name = f"r{classes[rules]}"
2418
2419 if style.reverse:
2420 has_background = True
2421 background = (
2422 _theme.foreground_color.hex
2423 if style.color is None
2424 else style.color.get_truecolor(_theme).hex
2425 )
2426 else:
2427 bgcolor = style.bgcolor
2428 has_background = bgcolor is not None and not bgcolor.is_default
2429 background = (
2430 _theme.background_color.hex
2431 if style.bgcolor is None
2432 else style.bgcolor.get_truecolor(_theme).hex
2433 )
2434
2435 text_length = cell_len(text)
2436 if has_background:
2437 text_backgrounds.append(
2438 make_tag(
2439 "rect",
2440 fill=background,
2441 x=x * char_width,
2442 y=y * line_height + 1.5,
2443 width=char_width * text_length,
2444 height=line_height + 0.25,
2445 shape_rendering="crispEdges",
2446 )
2447 )
2448
2449 if text != " " * len(text):
2450 text_group.append(
2451 make_tag(
2452 "text",
2453 escape_text(text),
2454 _class=f"{unique_id}-{class_name}",
2455 x=x * char_width,
2456 y=y * line_height + char_height,
2457 textLength=char_width * len(text),
2458 clip_path=f"url(#{unique_id}-line-{y})",
2459 )
2460 )
2461 x += cell_len(text)
2462
2463 line_offsets = [line_no * line_height + 1.5 for line_no in range(y)]
2464 lines = "\n".join(
2465 f"""<clipPath id="{unique_id}-line-{line_no}">
2466 {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)}
2467 </clipPath>"""
2468 for line_no, offset in enumerate(line_offsets)
2469 )
2470
2471 styles = "\n".join(
2472 f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items()
2473 )
2474 backgrounds = "".join(text_backgrounds)
2475 matrix = "".join(text_group)
2476
2477 terminal_width = ceil(width * char_width + padding_width)
2478 terminal_height = (y + 1) * line_height + padding_height
2479 chrome = make_tag(
2480 "rect",
2481 fill=_theme.background_color.hex,
2482 stroke="rgba(255,255,255,0.35)",
2483 stroke_width="1",
2484 x=margin_left,
2485 y=margin_top,
2486 width=terminal_width,
2487 height=terminal_height,
2488 rx=8,
2489 )
2490
2491 title_color = _theme.foreground_color.hex
2492 if title:
2493 chrome += make_tag(
2494 "text",
2495 escape_text(title),
2496 _class=f"{unique_id}-title",
2497 fill=title_color,
2498 text_anchor="middle",
2499 x=terminal_width // 2,
2500 y=margin_top + char_height + 6,
2501 )
2502 chrome += f"""
2503 <g transform="translate(26,22)">
2504 <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
2505 <circle cx="22" cy="0" r="7" fill="#febc2e"/>
2506 <circle cx="44" cy="0" r="7" fill="#28c840"/>
2507 </g>
2508 """
2509
2510 svg = code_format.format(
2511 unique_id=unique_id,
2512 char_width=char_width,
2513 char_height=char_height,
2514 line_height=line_height,
2515 terminal_width=char_width * width - 1,
2516 terminal_height=(y + 1) * line_height - 1,
2517 width=terminal_width + margin_width,
2518 height=terminal_height + margin_height,
2519 terminal_x=margin_left + padding_left,
2520 terminal_y=margin_top + padding_top,
2521 styles=styles,
2522 chrome=chrome,
2523 backgrounds=backgrounds,
2524 matrix=matrix,
2525 lines=lines,
2526 )
2527 return svg
2528
2529 def save_svg(
2530 self,
2531 path: str,
2532 *,
2533 title: str = "Rich",
2534 theme: Optional[TerminalTheme] = None,
2535 clear: bool = True,
2536 code_format: str = CONSOLE_SVG_FORMAT,
2537 font_aspect_ratio: float = 0.61,
2538 unique_id: Optional[str] = None,
2539 ) -> None:
2540 """Generate an SVG file from the console contents (requires record=True in Console constructor).
2541
2542 Args:
2543 path (str): The path to write the SVG to.
2544 title (str, optional): The title of the tab in the output image
2545 theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
2546 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
2547 code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
2548 into the string in order to form the final SVG output. The default template used and the variables
2549 injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
2550 font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
2551 string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
2552 If you aren't specifying a different font inside ``code_format``, you probably don't need this.
2553 unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
2554 ids). If not set, this defaults to a computed value based on the recorded content.
2555 """
2556 svg = self.export_svg(
2557 title=title,
2558 theme=theme,
2559 clear=clear,
2560 code_format=code_format,
2561 font_aspect_ratio=font_aspect_ratio,
2562 unique_id=unique_id,
2563 )
2564 with open(path, "wt", encoding="utf-8") as write_file:
2565 write_file.write(svg)
2566
2567
2568 def _svg_hash(svg_main_code: str) -> str:
2569 """Returns a unique hash for the given SVG main code.
2570
2571 Args:
2572 svg_main_code (str): The content we're going to inject in the SVG envelope.
2573
2574 Returns:
2575 str: a hash of the given content
2576 """
2577 return str(zlib.adler32(svg_main_code.encode()))
2578
2579
2580 if __name__ == "__main__": # pragma: no cover
2581 console = Console(record=True)
2582
2583 console.log(
2584 "JSONRPC [i]request[/i]",
2585 5,
2586 1.3,
2587 True,
2588 False,
2589 None,
2590 {
2591 "jsonrpc": "2.0",
2592 "method": "subtract",
2593 "params": {"minuend": 42, "subtrahend": 23},
2594 "id": 3,
2595 },
2596 )
2597
2598 console.log("Hello, World!", "{'a': 1}", repr(console))
2599
2600 console.print(
2601 {
2602 "name": None,
2603 "empty": [],
2604 "quiz": {
2605 "sport": {
2606 "answered": True,
2607 "q1": {
2608 "question": "Which one is correct team name in NBA?",
2609 "options": [
2610 "New York Bulls",
2611 "Los Angeles Kings",
2612 "Golden State Warriors",
2613 "Huston Rocket",
2614 ],
2615 "answer": "Huston Rocket",
2616 },
2617 },
2618 "maths": {
2619 "answered": False,
2620 "q1": {
2621 "question": "5 + 7 = ?",
2622 "options": [10, 11, 12, 13],
2623 "answer": 12,
2624 },
2625 "q2": {
2626 "question": "12 - 8 = ?",
2627 "options": [1, 2, 3, 4],
2628 "answer": 4,
2629 },
2630 },
2631 },
2632 }
2633 )