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