Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/console.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1009 statements  

1import os 

2import sys 

3import threading 

4from abc import ABC, abstractmethod 

5from dataclasses import dataclass, field 

6from datetime import datetime 

7from functools import wraps 

8from itertools import islice 

9from math import ceil 

10from os import PathLike 

11from time import monotonic 

12from types import FrameType, ModuleType, TracebackType 

13from typing import ( 

14 IO, 

15 TYPE_CHECKING, 

16 Any, 

17 Callable, 

18 Dict, 

19 Iterable, 

20 List, 

21 Literal, 

22 Mapping, 

23 NamedTuple, 

24 Optional, 

25 Protocol, 

26 TextIO, 

27 Tuple, 

28 Type, 

29 Union, 

30 cast, 

31 runtime_checkable, 

32) 

33 

34from rich._null_file import NULL_FILE 

35 

36from . import errors, themes 

37from ._emoji_replace import _emoji_replace 

38from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT 

39from ._fileno import get_fileno 

40from ._log_render import FormatTimeCallable, LogRender 

41from .align import Align, AlignMethod 

42from .color import ColorSystem, blend_rgb 

43from .control import Control 

44from .emoji import EmojiVariant 

45from .highlighter import NullHighlighter, ReprHighlighter 

46from .markup import render as render_markup 

47from .measure import Measurement, measure_renderables 

48from .pager import Pager, SystemPager 

49from .protocol import rich_cast 

50from .region import Region 

51from .screen import Screen 

52from .segment import Segment 

53from .style import Style, StyleType 

54from .styled import Styled 

55from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme 

56from .text import Text, TextType 

57from .theme import Theme, ThemeStack 

58 

59if TYPE_CHECKING: 

60 from ._windows import WindowsConsoleFeatures 

61 from .live import Live 

62 from .status import Status 

63 

64JUPYTER_DEFAULT_COLUMNS = 115 

65JUPYTER_DEFAULT_LINES = 100 

66WINDOWS = sys.platform == "win32" 

67 

68HighlighterType = Callable[[Union[str, "Text"]], "Text"] 

69JustifyMethod = Literal["default", "left", "center", "right", "full"] 

70OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"] 

71 

72 

73class NoChange: 

74 pass 

75 

76 

77NO_CHANGE = NoChange() 

78 

79try: 

80 _STDIN_FILENO = sys.__stdin__.fileno() # type: ignore[union-attr] 

81except Exception: 

82 _STDIN_FILENO = 0 

83try: 

84 _STDOUT_FILENO = sys.__stdout__.fileno() # type: ignore[union-attr] 

85except Exception: 

86 _STDOUT_FILENO = 1 

87try: 

88 _STDERR_FILENO = sys.__stderr__.fileno() # type: ignore[union-attr] 

89except Exception: 

90 _STDERR_FILENO = 2 

91 

92_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO) 

93_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO) 

94 

95 

96_TERM_COLORS = { 

97 "kitty": ColorSystem.EIGHT_BIT, 

98 "256color": ColorSystem.EIGHT_BIT, 

99 "16color": ColorSystem.STANDARD, 

100} 

101 

102 

103class ConsoleDimensions(NamedTuple): 

104 """Size of the terminal.""" 

105 

106 width: int 

107 """The width of the console in 'cells'.""" 

108 height: int 

109 """The height of the console in lines.""" 

110 

111 

112@dataclass 

113class ConsoleOptions: 

114 """Options for __rich_console__ method.""" 

115 

116 size: ConsoleDimensions 

117 """Size of console.""" 

118 legacy_windows: bool 

119 """legacy_windows: flag for legacy windows.""" 

120 min_width: int 

121 """Minimum width of renderable.""" 

122 max_width: int 

123 """Maximum width of renderable.""" 

124 is_terminal: bool 

125 """True if the target is a terminal, otherwise False.""" 

126 encoding: str 

127 """Encoding of terminal.""" 

128 max_height: int 

129 """Height of container (starts as terminal)""" 

130 justify: Optional[JustifyMethod] = None 

131 """Justify value override for renderable.""" 

132 overflow: Optional[OverflowMethod] = None 

133 """Overflow value override for renderable.""" 

134 no_wrap: Optional[bool] = False 

135 """Disable wrapping for text.""" 

136 highlight: Optional[bool] = None 

137 """Highlight override for render_str.""" 

138 markup: Optional[bool] = None 

139 """Enable markup when rendering strings.""" 

140 height: Optional[int] = None 

141 

142 @property 

143 def ascii_only(self) -> bool: 

144 """Check if renderables should use ascii only.""" 

145 return not self.encoding.startswith("utf") 

146 

147 def copy(self) -> "ConsoleOptions": 

148 """Return a copy of the options. 

149 

150 Returns: 

151 ConsoleOptions: a copy of self. 

152 """ 

153 options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions) 

154 options.__dict__ = self.__dict__.copy() 

155 return options 

156 

157 def update( 

158 self, 

159 *, 

160 width: Union[int, NoChange] = NO_CHANGE, 

161 min_width: Union[int, NoChange] = NO_CHANGE, 

162 max_width: Union[int, NoChange] = NO_CHANGE, 

163 justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE, 

164 overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE, 

165 no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE, 

166 highlight: Union[Optional[bool], NoChange] = NO_CHANGE, 

167 markup: Union[Optional[bool], NoChange] = NO_CHANGE, 

168 height: Union[Optional[int], NoChange] = NO_CHANGE, 

169 ) -> "ConsoleOptions": 

170 """Update values, return a copy.""" 

171 options = self.copy() 

172 if not isinstance(width, NoChange): 

173 options.min_width = options.max_width = max(0, width) 

174 if not isinstance(min_width, NoChange): 

175 options.min_width = min_width 

176 if not isinstance(max_width, NoChange): 

177 options.max_width = max_width 

178 if not isinstance(justify, NoChange): 

179 options.justify = justify 

180 if not isinstance(overflow, NoChange): 

181 options.overflow = overflow 

182 if not isinstance(no_wrap, NoChange): 

183 options.no_wrap = no_wrap 

184 if not isinstance(highlight, NoChange): 

185 options.highlight = highlight 

186 if not isinstance(markup, NoChange): 

187 options.markup = markup 

188 if not isinstance(height, NoChange): 

189 if height is not None: 

190 options.max_height = height 

191 options.height = None if height is None else max(0, height) 

192 return options 

193 

194 def update_width(self, width: int) -> "ConsoleOptions": 

195 """Update just the width, return a copy. 

196 

197 Args: 

198 width (int): New width (sets both min_width and max_width) 

199 

200 Returns: 

201 ~ConsoleOptions: New console options instance. 

202 """ 

203 options = self.copy() 

204 options.min_width = options.max_width = max(0, width) 

205 return options 

206 

207 def update_height(self, height: int) -> "ConsoleOptions": 

208 """Update the height, and return a copy. 

209 

210 Args: 

211 height (int): New height 

212 

213 Returns: 

214 ~ConsoleOptions: New Console options instance. 

215 """ 

216 options = self.copy() 

217 options.max_height = options.height = height 

218 return options 

219 

220 def reset_height(self) -> "ConsoleOptions": 

221 """Return a copy of the options with height set to ``None``. 

222 

223 Returns: 

224 ~ConsoleOptions: New console options instance. 

225 """ 

226 options = self.copy() 

227 options.height = None 

228 return options 

229 

230 def update_dimensions(self, width: int, height: int) -> "ConsoleOptions": 

231 """Update the width and height, and return a copy. 

232 

233 Args: 

234 width (int): New width (sets both min_width and max_width). 

235 height (int): New height. 

236 

237 Returns: 

238 ~ConsoleOptions: New console options instance. 

239 """ 

240 options = self.copy() 

241 options.min_width = options.max_width = max(0, width) 

242 options.height = options.max_height = height 

243 return options 

244 

245 

246@runtime_checkable 

247class RichCast(Protocol): 

248 """An object that may be 'cast' to a console renderable.""" 

249 

250 def __rich__( 

251 self, 

252 ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover 

253 ... 

254 

255 

256@runtime_checkable 

257class ConsoleRenderable(Protocol): 

258 """An object that supports the console protocol.""" 

259 

260 def __rich_console__( 

261 self, console: "Console", options: "ConsoleOptions" 

262 ) -> "RenderResult": # pragma: no cover 

263 ... 

264 

265 

266# A type that may be rendered by Console. 

267RenderableType = Union[ConsoleRenderable, RichCast, str] 

268"""A string or any object that may be rendered by Rich.""" 

269 

270# The result of calling a __rich_console__ method. 

271RenderResult = Iterable[Union[RenderableType, Segment]] 

272 

273_null_highlighter = NullHighlighter() 

274 

275 

276class CaptureError(Exception): 

277 """An error in the Capture context manager.""" 

278 

279 

280class NewLine: 

281 """A renderable to generate new line(s)""" 

282 

283 def __init__(self, count: int = 1) -> None: 

284 self.count = count 

285 

286 def __rich_console__( 

287 self, console: "Console", options: "ConsoleOptions" 

288 ) -> Iterable[Segment]: 

289 yield Segment("\n" * self.count) 

290 

291 

292class ScreenUpdate: 

293 """Render a list of lines at a given offset.""" 

294 

295 def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None: 

296 self._lines = lines 

297 self.x = x 

298 self.y = y 

299 

300 def __rich_console__( 

301 self, console: "Console", options: ConsoleOptions 

302 ) -> RenderResult: 

303 x = self.x 

304 move_to = Control.move_to 

305 for offset, line in enumerate(self._lines, self.y): 

306 yield move_to(x, offset) 

307 yield from line 

308 

309 

310class Capture: 

311 """Context manager to capture the result of printing to the console. 

312 See :meth:`~rich.console.Console.capture` for how to use. 

313 

314 Args: 

315 console (Console): A console instance to capture output. 

316 """ 

317 

318 def __init__(self, console: "Console") -> None: 

319 self._console = console 

320 self._result: Optional[str] = None 

321 

322 def __enter__(self) -> "Capture": 

323 self._console.begin_capture() 

324 return self 

325 

326 def __exit__( 

327 self, 

328 exc_type: Optional[Type[BaseException]], 

329 exc_val: Optional[BaseException], 

330 exc_tb: Optional[TracebackType], 

331 ) -> None: 

332 self._result = self._console.end_capture() 

333 

334 def get(self) -> str: 

335 """Get the result of the capture.""" 

336 if self._result is None: 

337 raise CaptureError( 

338 "Capture result is not available until context manager exits." 

339 ) 

340 return self._result 

341 

342 

343class ThemeContext: 

344 """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage.""" 

345 

346 def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None: 

347 self.console = console 

348 self.theme = theme 

349 self.inherit = inherit 

350 

351 def __enter__(self) -> "ThemeContext": 

352 self.console.push_theme(self.theme) 

353 return self 

354 

355 def __exit__( 

356 self, 

357 exc_type: Optional[Type[BaseException]], 

358 exc_val: Optional[BaseException], 

359 exc_tb: Optional[TracebackType], 

360 ) -> None: 

361 self.console.pop_theme() 

362 

363 

364class PagerContext: 

365 """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage.""" 

366 

367 def __init__( 

368 self, 

369 console: "Console", 

370 pager: Optional[Pager] = None, 

371 styles: bool = False, 

372 links: bool = False, 

373 ) -> None: 

374 self._console = console 

375 self.pager = SystemPager() if pager is None else pager 

376 self.styles = styles 

377 self.links = links 

378 

379 def __enter__(self) -> "PagerContext": 

380 self._console._enter_buffer() 

381 return self 

382 

383 def __exit__( 

384 self, 

385 exc_type: Optional[Type[BaseException]], 

386 exc_val: Optional[BaseException], 

387 exc_tb: Optional[TracebackType], 

388 ) -> None: 

389 if exc_type is None: 

390 with self._console._lock: 

391 buffer: List[Segment] = self._console._buffer[:] 

392 del self._console._buffer[:] 

393 segments: Iterable[Segment] = buffer 

394 if not self.styles: 

395 segments = Segment.strip_styles(segments) 

396 elif not self.links: 

397 segments = Segment.strip_links(segments) 

398 content = self._console._render_buffer(segments) 

399 self.pager.show(content) 

400 self._console._exit_buffer() 

401 

402 

403class ScreenContext: 

404 """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage.""" 

405 

406 def __init__( 

407 self, console: "Console", hide_cursor: bool, style: StyleType = "" 

408 ) -> None: 

409 self.console = console 

410 self.hide_cursor = hide_cursor 

411 self.screen = Screen(style=style) 

412 self._changed = False 

413 

414 def update( 

415 self, *renderables: RenderableType, style: Optional[StyleType] = None 

416 ) -> None: 

417 """Update the screen. 

418 

419 Args: 

420 renderable (RenderableType, optional): Optional renderable to replace current renderable, 

421 or None for no change. Defaults to None. 

422 style: (Style, optional): Replacement style, or None for no change. Defaults to None. 

423 """ 

424 if renderables: 

425 self.screen.renderable = ( 

426 Group(*renderables) if len(renderables) > 1 else renderables[0] 

427 ) 

428 if style is not None: 

429 self.screen.style = style 

430 self.console.print(self.screen, end="") 

431 

432 def __enter__(self) -> "ScreenContext": 

433 self._changed = self.console.set_alt_screen(True) 

434 if self._changed and self.hide_cursor: 

435 self.console.show_cursor(False) 

436 return self 

437 

438 def __exit__( 

439 self, 

440 exc_type: Optional[Type[BaseException]], 

441 exc_val: Optional[BaseException], 

442 exc_tb: Optional[TracebackType], 

443 ) -> None: 

444 if self._changed: 

445 self.console.set_alt_screen(False) 

446 if self.hide_cursor: 

447 self.console.show_cursor(True) 

448 

449 

450class Group: 

451 """Takes a group of renderables and returns a renderable object that renders the group. 

452 

453 Args: 

454 renderables (Iterable[RenderableType]): An iterable of renderable objects. 

455 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. 

456 """ 

457 

458 def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None: 

459 self._renderables = renderables 

460 self.fit = fit 

461 self._render: Optional[List[RenderableType]] = None 

462 

463 @property 

464 def renderables(self) -> List["RenderableType"]: 

465 if self._render is None: 

466 self._render = list(self._renderables) 

467 return self._render 

468 

469 def __rich_measure__( 

470 self, console: "Console", options: "ConsoleOptions" 

471 ) -> "Measurement": 

472 if self.fit: 

473 return measure_renderables(console, options, self.renderables) 

474 else: 

475 return Measurement(options.max_width, options.max_width) 

476 

477 def __rich_console__( 

478 self, console: "Console", options: "ConsoleOptions" 

479 ) -> RenderResult: 

480 yield from self.renderables 

481 

482 

483def group(fit: bool = True) -> Callable[..., Callable[..., Group]]: 

484 """A decorator that turns an iterable of renderables in to a group. 

485 

486 Args: 

487 fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. 

488 """ 

489 

490 def decorator( 

491 method: Callable[..., Iterable[RenderableType]], 

492 ) -> Callable[..., Group]: 

493 """Convert a method that returns an iterable of renderables in to a Group.""" 

494 

495 @wraps(method) 

496 def _replace(*args: Any, **kwargs: Any) -> Group: 

497 renderables = method(*args, **kwargs) 

498 return Group(*renderables, fit=fit) 

499 

500 return _replace 

501 

502 return decorator 

503 

504 

505def _is_jupyter() -> bool: # pragma: no cover 

506 """Check if we're running in a Jupyter notebook.""" 

507 try: 

508 get_ipython # type: ignore[name-defined] 

509 except NameError: 

510 return False 

511 ipython = get_ipython() # type: ignore[name-defined] 

512 shell = ipython.__class__.__name__ 

513 if ( 

514 "google.colab" in str(ipython.__class__) 

515 or os.getenv("DATABRICKS_RUNTIME_VERSION") 

516 or shell == "ZMQInteractiveShell" 

517 ): 

518 return True # Jupyter notebook or qtconsole 

519 elif shell == "TerminalInteractiveShell": 

520 return False # Terminal running IPython 

521 else: 

522 return False # Other type (?) 

523 

524 

525COLOR_SYSTEMS = { 

526 "standard": ColorSystem.STANDARD, 

527 "256": ColorSystem.EIGHT_BIT, 

528 "truecolor": ColorSystem.TRUECOLOR, 

529 "windows": ColorSystem.WINDOWS, 

530} 

531 

532_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()} 

533 

534 

535@dataclass 

536class ConsoleThreadLocals(threading.local): 

537 """Thread local values for Console context.""" 

538 

539 theme_stack: ThemeStack 

540 buffer: List[Segment] = field(default_factory=list) 

541 buffer_index: int = 0 

542 

543 

544class RenderHook(ABC): 

545 """Provides hooks in to the render process.""" 

546 

547 @abstractmethod 

548 def process_renderables( 

549 self, renderables: List[ConsoleRenderable] 

550 ) -> List[ConsoleRenderable]: 

551 """Called with a list of objects to render. 

552 

553 This method can return a new list of renderables, or modify and return the same list. 

554 

555 Args: 

556 renderables (List[ConsoleRenderable]): A number of renderable objects. 

557 

558 Returns: 

559 List[ConsoleRenderable]: A replacement list of renderables. 

560 """ 

561 

562 

563_windows_console_features: Optional["WindowsConsoleFeatures"] = None 

564 

565 

566def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover 

567 global _windows_console_features 

568 if _windows_console_features is not None: 

569 return _windows_console_features 

570 from ._windows import get_windows_console_features 

571 

572 _windows_console_features = get_windows_console_features() 

573 return _windows_console_features 

574 

575 

576def detect_legacy_windows() -> bool: 

577 """Detect legacy Windows.""" 

578 return WINDOWS and not get_windows_console_features().vt 

579 

580 

581class Console: 

582 """A high level console interface. 

583 

584 Args: 

585 color_system (str, optional): The color system supported by your terminal, 

586 either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect. 

587 force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None. 

588 force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None. 

589 force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None. 

590 soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False. 

591 theme (Theme, optional): An optional style theme object, or ``None`` for default theme. 

592 stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False. 

593 file (IO, optional): A file object where the console should write to. Defaults to stdout. 

594 quiet (bool, Optional): Boolean to suppress all output. Defaults to False. 

595 width (int, optional): The width of the terminal. Leave as default to auto-detect width. 

596 height (int, optional): The height of the terminal. Leave as default to auto-detect height. 

597 style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None. 

598 no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None. 

599 tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8. 

600 record (bool, optional): Boolean to enable recording of terminal output, 

601 required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False. 

602 markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True. 

603 emoji (bool, optional): Enable emoji code. Defaults to True. 

604 emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. 

605 highlight (bool, optional): Enable automatic highlighting. Defaults to True. 

606 log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True. 

607 log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True. 

608 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] ". 

609 highlighter (HighlighterType, optional): Default highlighter. 

610 legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``. 

611 safe_box (bool, optional): Restrict box options that don't render on legacy Windows. 

612 get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log), 

613 or None for datetime.now. 

614 get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic. 

615 """ 

616 

617 _environ: Mapping[str, str] = os.environ 

618 

619 def __init__( 

620 self, 

621 *, 

622 color_system: Optional[ 

623 Literal["auto", "standard", "256", "truecolor", "windows"] 

624 ] = "auto", 

625 force_terminal: Optional[bool] = None, 

626 force_jupyter: Optional[bool] = None, 

627 force_interactive: Optional[bool] = None, 

628 soft_wrap: bool = False, 

629 theme: Optional[Theme] = None, 

630 stderr: bool = False, 

631 file: Optional[IO[str]] = None, 

632 quiet: bool = False, 

633 width: Optional[int] = None, 

634 height: Optional[int] = None, 

635 style: Optional[StyleType] = None, 

636 no_color: Optional[bool] = None, 

637 tab_size: int = 8, 

638 record: bool = False, 

639 markup: bool = True, 

640 emoji: bool = True, 

641 emoji_variant: Optional[EmojiVariant] = None, 

642 highlight: bool = True, 

643 log_time: bool = True, 

644 log_path: bool = True, 

645 log_time_format: Union[str, FormatTimeCallable] = "[%X]", 

646 highlighter: Optional["HighlighterType"] = ReprHighlighter(), 

647 legacy_windows: Optional[bool] = None, 

648 safe_box: bool = True, 

649 get_datetime: Optional[Callable[[], datetime]] = None, 

650 get_time: Optional[Callable[[], float]] = None, 

651 _environ: Optional[Mapping[str, str]] = None, 

652 ): 

653 # Copy of os.environ allows us to replace it for testing 

654 if _environ is not None: 

655 self._environ = _environ 

656 

657 self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter 

658 if self.is_jupyter: 

659 if width is None: 

660 jupyter_columns = self._environ.get("JUPYTER_COLUMNS") 

661 if jupyter_columns is not None and jupyter_columns.isdigit(): 

662 width = int(jupyter_columns) 

663 else: 

664 width = JUPYTER_DEFAULT_COLUMNS 

665 if height is None: 

666 jupyter_lines = self._environ.get("JUPYTER_LINES") 

667 if jupyter_lines is not None and jupyter_lines.isdigit(): 

668 height = int(jupyter_lines) 

669 else: 

670 height = JUPYTER_DEFAULT_LINES 

671 

672 self.tab_size = tab_size 

673 self.record = record 

674 self._markup = markup 

675 self._emoji = emoji 

676 self._emoji_variant: Optional[EmojiVariant] = emoji_variant 

677 self._highlight = highlight 

678 self.legacy_windows: bool = ( 

679 (detect_legacy_windows() and not self.is_jupyter) 

680 if legacy_windows is None 

681 else legacy_windows 

682 ) 

683 

684 if width is None: 

685 columns = self._environ.get("COLUMNS") 

686 if columns is not None and columns.isdigit(): 

687 width = int(columns) - self.legacy_windows 

688 if height is None: 

689 lines = self._environ.get("LINES") 

690 if lines is not None and lines.isdigit(): 

691 height = int(lines) 

692 

693 self.soft_wrap = soft_wrap 

694 self._width = width 

695 self._height = height 

696 

697 self._color_system: Optional[ColorSystem] 

698 

699 self._force_terminal = None 

700 if force_terminal is not None: 

701 self._force_terminal = force_terminal 

702 

703 self._file = file 

704 self.quiet = quiet 

705 self.stderr = stderr 

706 

707 if color_system is None: 

708 self._color_system = None 

709 elif color_system == "auto": 

710 self._color_system = self._detect_color_system() 

711 else: 

712 self._color_system = COLOR_SYSTEMS[color_system] 

713 

714 self._lock = threading.RLock() 

715 self._log_render = LogRender( 

716 show_time=log_time, 

717 show_path=log_path, 

718 time_format=log_time_format, 

719 ) 

720 self.highlighter: HighlighterType = highlighter or _null_highlighter 

721 self.safe_box = safe_box 

722 self.get_datetime = get_datetime or datetime.now 

723 self.get_time = get_time or monotonic 

724 self.style = style 

725 self.no_color = ( 

726 no_color 

727 if no_color is not None 

728 else self._environ.get("NO_COLOR", "") != "" 

729 ) 

730 if force_interactive is None: 

731 tty_interactive = self._environ.get("TTY_INTERACTIVE", None) 

732 if tty_interactive is not None: 

733 if tty_interactive == "0": 

734 force_interactive = False 

735 elif tty_interactive == "1": 

736 force_interactive = True 

737 

738 self.is_interactive = ( 

739 (self.is_terminal and not self.is_dumb_terminal) 

740 if force_interactive is None 

741 else force_interactive 

742 ) 

743 

744 self._record_buffer_lock = threading.RLock() 

745 self._thread_locals = ConsoleThreadLocals( 

746 theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme) 

747 ) 

748 self._record_buffer: List[Segment] = [] 

749 self._render_hooks: List[RenderHook] = [] 

750 self._live_stack: List[Live] = [] 

751 self._is_alt_screen = False 

752 

753 def __repr__(self) -> str: 

754 return f"<console width={self.width} {self._color_system!s}>" 

755 

756 @property 

757 def file(self) -> IO[str]: 

758 """Get the file object to write to.""" 

759 file = self._file or (sys.stderr if self.stderr else sys.stdout) 

760 file = getattr(file, "rich_proxied_file", file) 

761 if file is None: 

762 file = NULL_FILE 

763 return file 

764 

765 @file.setter 

766 def file(self, new_file: IO[str]) -> None: 

767 """Set a new file object.""" 

768 self._file = new_file 

769 

770 @property 

771 def _buffer(self) -> List[Segment]: 

772 """Get a thread local buffer.""" 

773 return self._thread_locals.buffer 

774 

775 @property 

776 def _buffer_index(self) -> int: 

777 """Get a thread local buffer.""" 

778 return self._thread_locals.buffer_index 

779 

780 @_buffer_index.setter 

781 def _buffer_index(self, value: int) -> None: 

782 self._thread_locals.buffer_index = value 

783 

784 @property 

785 def _theme_stack(self) -> ThemeStack: 

786 """Get the thread local theme stack.""" 

787 return self._thread_locals.theme_stack 

788 

789 def _detect_color_system(self) -> Optional[ColorSystem]: 

790 """Detect color system from env vars.""" 

791 if self.is_jupyter: 

792 return ColorSystem.TRUECOLOR 

793 if not self.is_terminal or self.is_dumb_terminal: 

794 return None 

795 if WINDOWS: # pragma: no cover 

796 if self.legacy_windows: # pragma: no cover 

797 return ColorSystem.WINDOWS 

798 windows_console_features = get_windows_console_features() 

799 return ( 

800 ColorSystem.TRUECOLOR 

801 if windows_console_features.truecolor 

802 else ColorSystem.EIGHT_BIT 

803 ) 

804 else: 

805 color_term = self._environ.get("COLORTERM", "").strip().lower() 

806 if color_term in ("truecolor", "24bit"): 

807 return ColorSystem.TRUECOLOR 

808 term = self._environ.get("TERM", "").strip().lower() 

809 _term_name, _hyphen, colors = term.rpartition("-") 

810 color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD) 

811 return color_system 

812 

813 def _enter_buffer(self) -> None: 

814 """Enter in to a buffer context, and buffer all output.""" 

815 self._buffer_index += 1 

816 

817 def _exit_buffer(self) -> None: 

818 """Leave buffer context, and render content if required.""" 

819 self._buffer_index -= 1 

820 self._check_buffer() 

821 

822 def set_live(self, live: "Live") -> bool: 

823 """Set Live instance. Used by Live context manager (no need to call directly). 

824 

825 Args: 

826 live (Live): Live instance using this Console. 

827 

828 Returns: 

829 Boolean that indicates if the live is the topmost of the stack. 

830 

831 Raises: 

832 errors.LiveError: If this Console has a Live context currently active. 

833 """ 

834 with self._lock: 

835 self._live_stack.append(live) 

836 return len(self._live_stack) == 1 

837 

838 def clear_live(self) -> None: 

839 """Clear the Live instance. Used by the Live context manager (no need to call directly).""" 

840 with self._lock: 

841 self._live_stack.pop() 

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 escape sequences, otherwise False. 

937 """ 

938 # If dev has explicitly set this value, return it 

939 if self._force_terminal is not None: 

940 return self._force_terminal 

941 

942 # Fudge for Idle 

943 if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith( 

944 "idlelib" 

945 ): 

946 # Return False for Idle which claims to be a tty but can't handle ansi codes 

947 return False 

948 

949 if self.is_jupyter: 

950 # return False for Jupyter, which may have FORCE_COLOR set 

951 return False 

952 

953 environ = self._environ 

954 

955 tty_compatible = environ.get("TTY_COMPATIBLE", "") 

956 # 0 indicates device is not tty compatible 

957 if tty_compatible == "0": 

958 return False 

959 # 1 indicates device is tty compatible 

960 if tty_compatible == "1": 

961 return True 

962 

963 # https://force-color.org/ 

964 force_color = environ.get("FORCE_COLOR") 

965 if force_color is not None: 

966 return force_color != "" 

967 

968 # Any other value defaults to auto detect 

969 isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None) 

970 try: 

971 return False if isatty is None else isatty() 

972 except ValueError: 

973 # in some situation (at the end of a pytest run for example) isatty() can raise 

974 # ValueError: I/O operation on closed file 

975 # return False because we aren't in a terminal anymore 

976 return False 

977 

978 @property 

979 def is_dumb_terminal(self) -> bool: 

980 """Detect dumb terminal. 

981 

982 Returns: 

983 bool: True if writing to a dumb terminal, otherwise False. 

984 

985 """ 

986 _term = self._environ.get("TERM", "") 

987 is_dumb = _term.lower() in ("dumb", "unknown") 

988 return self.is_terminal and is_dumb 

989 

990 @property 

991 def options(self) -> ConsoleOptions: 

992 """Get default console options.""" 

993 size = self.size 

994 return ConsoleOptions( 

995 max_height=size.height, 

996 size=size, 

997 legacy_windows=self.legacy_windows, 

998 min_width=1, 

999 max_width=size.width, 

1000 encoding=self.encoding, 

1001 is_terminal=self.is_terminal, 

1002 ) 

1003 

1004 @property 

1005 def size(self) -> ConsoleDimensions: 

1006 """Get the size of the console. 

1007 

1008 Returns: 

1009 ConsoleDimensions: A named tuple containing the dimensions. 

1010 """ 

1011 

1012 if self._width is not None and self._height is not None: 

1013 return ConsoleDimensions(self._width - self.legacy_windows, self._height) 

1014 

1015 if self.is_dumb_terminal: 

1016 return ConsoleDimensions(80, 25) 

1017 

1018 width: Optional[int] = None 

1019 height: Optional[int] = None 

1020 

1021 streams = _STD_STREAMS_OUTPUT if WINDOWS else _STD_STREAMS 

1022 for file_descriptor in streams: 

1023 try: 

1024 width, height = os.get_terminal_size(file_descriptor) 

1025 except (AttributeError, ValueError, OSError): # Probably not a terminal 

1026 pass 

1027 else: 

1028 break 

1029 

1030 columns = self._environ.get("COLUMNS") 

1031 if columns is not None and columns.isdigit(): 

1032 width = int(columns) 

1033 lines = self._environ.get("LINES") 

1034 if lines is not None and lines.isdigit(): 

1035 height = int(lines) 

1036 

1037 # get_terminal_size can report 0, 0 if run from pseudo-terminal 

1038 width = width or 80 

1039 height = height or 25 

1040 return ConsoleDimensions( 

1041 width - self.legacy_windows if self._width is None else self._width, 

1042 height if self._height is None else self._height, 

1043 ) 

1044 

1045 @size.setter 

1046 def size(self, new_size: Tuple[int, int]) -> None: 

1047 """Set a new size for the terminal. 

1048 

1049 Args: 

1050 new_size (Tuple[int, int]): New width and height. 

1051 """ 

1052 width, height = new_size 

1053 self._width = width 

1054 self._height = height 

1055 

1056 @property 

1057 def width(self) -> int: 

1058 """Get the width of the console. 

1059 

1060 Returns: 

1061 int: The width (in characters) of the console. 

1062 """ 

1063 return self.size.width 

1064 

1065 @width.setter 

1066 def width(self, width: int) -> None: 

1067 """Set width. 

1068 

1069 Args: 

1070 width (int): New width. 

1071 """ 

1072 self._width = width 

1073 

1074 @property 

1075 def height(self) -> int: 

1076 """Get the height of the console. 

1077 

1078 Returns: 

1079 int: The height (in lines) of the console. 

1080 """ 

1081 return self.size.height 

1082 

1083 @height.setter 

1084 def height(self, height: int) -> None: 

1085 """Set height. 

1086 

1087 Args: 

1088 height (int): new height. 

1089 """ 

1090 self._height = height 

1091 

1092 def bell(self) -> None: 

1093 """Play a 'bell' sound (if supported by the terminal).""" 

1094 self.control(Control.bell()) 

1095 

1096 def capture(self) -> Capture: 

1097 """A context manager to *capture* the result of print() or log() in a string, 

1098 rather than writing it to the console. 

1099 

1100 Example: 

1101 >>> from rich.console import Console 

1102 >>> console = Console() 

1103 >>> with console.capture() as capture: 

1104 ... console.print("[bold magenta]Hello World[/]") 

1105 >>> print(capture.get()) 

1106 

1107 Returns: 

1108 Capture: Context manager with disables writing to the terminal. 

1109 """ 

1110 capture = Capture(self) 

1111 return capture 

1112 

1113 def pager( 

1114 self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False 

1115 ) -> PagerContext: 

1116 """A context manager to display anything printed within a "pager". The pager application 

1117 is defined by the system and will typically support at least pressing a key to scroll. 

1118 

1119 Args: 

1120 pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None. 

1121 styles (bool, optional): Show styles in pager. Defaults to False. 

1122 links (bool, optional): Show links in pager. Defaults to False. 

1123 

1124 Example: 

1125 >>> from rich.console import Console 

1126 >>> from rich.__main__ import make_test_card 

1127 >>> console = Console() 

1128 >>> with console.pager(): 

1129 console.print(make_test_card()) 

1130 

1131 Returns: 

1132 PagerContext: A context manager. 

1133 """ 

1134 return PagerContext(self, pager=pager, styles=styles, links=links) 

1135 

1136 def line(self, count: int = 1) -> None: 

1137 """Write new line(s). 

1138 

1139 Args: 

1140 count (int, optional): Number of new lines. Defaults to 1. 

1141 """ 

1142 

1143 assert count >= 0, "count must be >= 0" 

1144 self.print(NewLine(count)) 

1145 

1146 def clear(self, home: bool = True) -> None: 

1147 """Clear the screen. 

1148 

1149 Args: 

1150 home (bool, optional): Also move the cursor to 'home' position. Defaults to True. 

1151 """ 

1152 if home: 

1153 self.control(Control.clear(), Control.home()) 

1154 else: 

1155 self.control(Control.clear()) 

1156 

1157 def status( 

1158 self, 

1159 status: RenderableType, 

1160 *, 

1161 spinner: str = "dots", 

1162 spinner_style: StyleType = "status.spinner", 

1163 speed: float = 1.0, 

1164 refresh_per_second: float = 12.5, 

1165 ) -> "Status": 

1166 """Display a status and spinner. 

1167 

1168 Args: 

1169 status (RenderableType): A status renderable (str or Text typically). 

1170 spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots". 

1171 spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner". 

1172 speed (float, optional): Speed factor for spinner animation. Defaults to 1.0. 

1173 refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5. 

1174 

1175 Returns: 

1176 Status: A Status object that may be used as a context manager. 

1177 """ 

1178 from .status import Status 

1179 

1180 status_renderable = Status( 

1181 status, 

1182 console=self, 

1183 spinner=spinner, 

1184 spinner_style=spinner_style, 

1185 speed=speed, 

1186 refresh_per_second=refresh_per_second, 

1187 ) 

1188 return status_renderable 

1189 

1190 def show_cursor(self, show: bool = True) -> bool: 

1191 """Show or hide the cursor. 

1192 

1193 Args: 

1194 show (bool, optional): Set visibility of the cursor. 

1195 """ 

1196 if self.is_terminal: 

1197 self.control(Control.show_cursor(show)) 

1198 return True 

1199 return False 

1200 

1201 def set_alt_screen(self, enable: bool = True) -> bool: 

1202 """Enables alternative screen mode. 

1203 

1204 Note, if you enable this mode, you should ensure that is disabled before 

1205 the application exits. See :meth:`~rich.Console.screen` for a context manager 

1206 that handles this for you. 

1207 

1208 Args: 

1209 enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True. 

1210 

1211 Returns: 

1212 bool: True if the control codes were written. 

1213 

1214 """ 

1215 changed = False 

1216 if self.is_terminal and not self.legacy_windows: 

1217 self.control(Control.alt_screen(enable)) 

1218 changed = True 

1219 self._is_alt_screen = enable 

1220 return changed 

1221 

1222 @property 

1223 def is_alt_screen(self) -> bool: 

1224 """Check if the alt screen was enabled. 

1225 

1226 Returns: 

1227 bool: True if the alt screen was enabled, otherwise False. 

1228 """ 

1229 return self._is_alt_screen 

1230 

1231 def set_window_title(self, title: str) -> bool: 

1232 """Set the title of the console terminal window. 

1233 

1234 Warning: There is no means within Rich of "resetting" the window title to its 

1235 previous value, meaning the title you set will persist even after your application 

1236 exits. 

1237 

1238 ``fish`` shell resets the window title before and after each command by default, 

1239 negating this issue. Windows Terminal and command prompt will also reset the title for you. 

1240 Most other shells and terminals, however, do not do this. 

1241 

1242 Some terminals may require configuration changes before you can set the title. 

1243 Some terminals may not support setting the title at all. 

1244 

1245 Other software (including the terminal itself, the shell, custom prompts, plugins, etc.) 

1246 may also set the terminal window title. This could result in whatever value you write 

1247 using this method being overwritten. 

1248 

1249 Args: 

1250 title (str): The new title of the terminal window. 

1251 

1252 Returns: 

1253 bool: True if the control code to change the terminal title was 

1254 written, otherwise False. Note that a return value of True 

1255 does not guarantee that the window title has actually changed, 

1256 since the feature may be unsupported/disabled in some terminals. 

1257 """ 

1258 if self.is_terminal: 

1259 self.control(Control.title(title)) 

1260 return True 

1261 return False 

1262 

1263 def screen( 

1264 self, hide_cursor: bool = True, style: Optional[StyleType] = None 

1265 ) -> "ScreenContext": 

1266 """Context manager to enable and disable 'alternative screen' mode. 

1267 

1268 Args: 

1269 hide_cursor (bool, optional): Also hide the cursor. Defaults to False. 

1270 style (Style, optional): Optional style for screen. Defaults to None. 

1271 

1272 Returns: 

1273 ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit. 

1274 """ 

1275 return ScreenContext(self, hide_cursor=hide_cursor, style=style or "") 

1276 

1277 def measure( 

1278 self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None 

1279 ) -> Measurement: 

1280 """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains 

1281 information regarding the number of characters required to print the renderable. 

1282 

1283 Args: 

1284 renderable (RenderableType): Any renderable or string. 

1285 options (Optional[ConsoleOptions], optional): Options to use when measuring, or None 

1286 to use default options. Defaults to None. 

1287 

1288 Returns: 

1289 Measurement: A measurement of the renderable. 

1290 """ 

1291 measurement = Measurement.get(self, options or self.options, renderable) 

1292 return measurement 

1293 

1294 def render( 

1295 self, renderable: RenderableType, options: Optional[ConsoleOptions] = None 

1296 ) -> Iterable[Segment]: 

1297 """Render an object in to an iterable of `Segment` instances. 

1298 

1299 This method contains the logic for rendering objects with the console protocol. 

1300 You are unlikely to need to use it directly, unless you are extending the library. 

1301 

1302 Args: 

1303 renderable (RenderableType): An object supporting the console protocol, or 

1304 an object that may be converted to a string. 

1305 options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None. 

1306 

1307 Returns: 

1308 Iterable[Segment]: An iterable of segments that may be rendered. 

1309 """ 

1310 

1311 _options = options or self.options 

1312 if _options.max_width < 1: 

1313 # No space to render anything. This prevents potential recursion errors. 

1314 return 

1315 render_iterable: RenderResult 

1316 

1317 renderable = rich_cast(renderable) 

1318 if hasattr(renderable, "__rich_console__") and not isinstance(renderable, type): 

1319 render_iterable = renderable.__rich_console__(self, _options) 

1320 elif isinstance(renderable, str): 

1321 text_renderable = self.render_str( 

1322 renderable, highlight=_options.highlight, markup=_options.markup 

1323 ) 

1324 render_iterable = text_renderable.__rich_console__(self, _options) 

1325 else: 

1326 raise errors.NotRenderableError( 

1327 f"Unable to render {renderable!r}; " 

1328 "A str, Segment or object with __rich_console__ method is required" 

1329 ) 

1330 

1331 try: 

1332 iter_render = iter(render_iterable) 

1333 except TypeError: 

1334 raise errors.NotRenderableError( 

1335 f"object {render_iterable!r} is not renderable" 

1336 ) 

1337 _Segment = Segment 

1338 _options = _options.reset_height() 

1339 for render_output in iter_render: 

1340 if isinstance(render_output, _Segment): 

1341 yield render_output 

1342 else: 

1343 yield from self.render(render_output, _options) 

1344 

1345 def render_lines( 

1346 self, 

1347 renderable: RenderableType, 

1348 options: Optional[ConsoleOptions] = None, 

1349 *, 

1350 style: Optional[Style] = None, 

1351 pad: bool = True, 

1352 new_lines: bool = False, 

1353 ) -> List[List[Segment]]: 

1354 """Render objects in to a list of lines. 

1355 

1356 The output of render_lines is useful when further formatting of rendered console text 

1357 is required, such as the Panel class which draws a border around any renderable object. 

1358 

1359 Args: 

1360 renderable (RenderableType): Any object renderable in the console. 

1361 options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``. 

1362 style (Style, optional): Optional style to apply to renderables. Defaults to ``None``. 

1363 pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``. 

1364 new_lines (bool, optional): Include "\n" characters at end of lines. 

1365 

1366 Returns: 

1367 List[List[Segment]]: A list of lines, where a line is a list of Segment objects. 

1368 """ 

1369 with self._lock: 

1370 render_options = options or self.options 

1371 _rendered = self.render(renderable, render_options) 

1372 if style: 

1373 _rendered = Segment.apply_style(_rendered, style) 

1374 

1375 render_height = render_options.height 

1376 if render_height is not None: 

1377 render_height = max(0, render_height) 

1378 

1379 lines = list( 

1380 islice( 

1381 Segment.split_and_crop_lines( 

1382 _rendered, 

1383 render_options.max_width, 

1384 include_new_lines=new_lines, 

1385 pad=pad, 

1386 style=style, 

1387 ), 

1388 None, 

1389 render_height, 

1390 ) 

1391 ) 

1392 if render_options.height is not None: 

1393 extra_lines = render_options.height - len(lines) 

1394 if extra_lines > 0: 

1395 pad_line = [ 

1396 ( 

1397 [ 

1398 Segment(" " * render_options.max_width, style), 

1399 Segment("\n"), 

1400 ] 

1401 if new_lines 

1402 else [Segment(" " * render_options.max_width, style)] 

1403 ) 

1404 ] 

1405 lines.extend(pad_line * extra_lines) 

1406 

1407 return lines 

1408 

1409 def render_str( 

1410 self, 

1411 text: str, 

1412 *, 

1413 style: Union[str, Style] = "", 

1414 justify: Optional[JustifyMethod] = None, 

1415 overflow: Optional[OverflowMethod] = None, 

1416 emoji: Optional[bool] = None, 

1417 markup: Optional[bool] = None, 

1418 highlight: Optional[bool] = None, 

1419 highlighter: Optional[HighlighterType] = None, 

1420 ) -> "Text": 

1421 """Convert a string to a Text instance. This is called automatically if 

1422 you print or log a string. 

1423 

1424 Args: 

1425 text (str): Text to render. 

1426 style (Union[str, Style], optional): Style to apply to rendered text. 

1427 justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``. 

1428 overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``. 

1429 emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default. 

1430 markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default. 

1431 highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default. 

1432 highlighter (HighlighterType, optional): Optional highlighter to apply. 

1433 Returns: 

1434 ConsoleRenderable: Renderable object. 

1435 

1436 """ 

1437 emoji_enabled = emoji or (emoji is None and self._emoji) 

1438 markup_enabled = markup or (markup is None and self._markup) 

1439 highlight_enabled = highlight or (highlight is None and self._highlight) 

1440 

1441 if markup_enabled: 

1442 rich_text = render_markup( 

1443 text, 

1444 style=style, 

1445 emoji=emoji_enabled, 

1446 emoji_variant=self._emoji_variant, 

1447 ) 

1448 rich_text.justify = justify 

1449 rich_text.overflow = overflow 

1450 else: 

1451 rich_text = Text( 

1452 ( 

1453 _emoji_replace(text, default_variant=self._emoji_variant) 

1454 if emoji_enabled 

1455 else text 

1456 ), 

1457 justify=justify, 

1458 overflow=overflow, 

1459 style=style, 

1460 ) 

1461 

1462 _highlighter = (highlighter or self.highlighter) if highlight_enabled else None 

1463 if _highlighter is not None: 

1464 highlight_text = _highlighter(str(rich_text)) 

1465 highlight_text.copy_styles(rich_text) 

1466 return highlight_text 

1467 

1468 return rich_text 

1469 

1470 def get_style( 

1471 self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None 

1472 ) -> Style: 

1473 """Get a Style instance by its theme name or parse a definition. 

1474 

1475 Args: 

1476 name (str): The name of a style or a style definition. 

1477 

1478 Returns: 

1479 Style: A Style object. 

1480 

1481 Raises: 

1482 MissingStyle: If no style could be parsed from name. 

1483 

1484 """ 

1485 if isinstance(name, Style): 

1486 return name 

1487 

1488 try: 

1489 style = self._theme_stack.get(name) 

1490 if style is None: 

1491 style = Style.parse(name) 

1492 return style.copy() if style.link else style 

1493 except errors.StyleSyntaxError as error: 

1494 if default is not None: 

1495 return self.get_style(default) 

1496 raise errors.MissingStyle( 

1497 f"Failed to get style {name!r}; {error}" 

1498 ) from None 

1499 

1500 def _collect_renderables( 

1501 self, 

1502 objects: Iterable[Any], 

1503 sep: str, 

1504 end: str, 

1505 *, 

1506 justify: Optional[JustifyMethod] = None, 

1507 emoji: Optional[bool] = None, 

1508 markup: Optional[bool] = None, 

1509 highlight: Optional[bool] = None, 

1510 ) -> List[ConsoleRenderable]: 

1511 """Combine a number of renderables and text into one renderable. 

1512 

1513 Args: 

1514 objects (Iterable[Any]): Anything that Rich can render. 

1515 sep (str): String to write between print data. 

1516 end (str): String to write at end of print data. 

1517 justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. 

1518 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. 

1519 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. 

1520 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. 

1521 

1522 Returns: 

1523 List[ConsoleRenderable]: A list of things to render. 

1524 """ 

1525 

1526 def is_expandable(obj: object) -> bool: 

1527 """Check if an object is expandable by pretty printer.""" 

1528 # Permit lazy loading 

1529 from .pretty import is_expandable as _is_expandable 

1530 

1531 return _is_expandable(obj) 

1532 

1533 renderables: List[ConsoleRenderable] = [] 

1534 _append = renderables.append 

1535 text: List[Text] = [] 

1536 append_text = text.append 

1537 

1538 append = _append 

1539 if justify in ("left", "center", "right"): 

1540 

1541 def align_append(renderable: RenderableType) -> None: 

1542 _append(Align(renderable, cast(AlignMethod, justify))) 

1543 

1544 append = align_append 

1545 

1546 _highlighter: HighlighterType = _null_highlighter 

1547 if highlight or (highlight is None and self._highlight): 

1548 _highlighter = self.highlighter 

1549 

1550 def check_text() -> None: 

1551 if text: 

1552 sep_text = Text(sep, justify=justify, end=end) 

1553 append(sep_text.join(text)) 

1554 text.clear() 

1555 

1556 for renderable in objects: 

1557 renderable = rich_cast(renderable) 

1558 if isinstance(renderable, str): 

1559 append_text( 

1560 self.render_str( 

1561 renderable, 

1562 emoji=emoji, 

1563 markup=markup, 

1564 highlight=highlight, 

1565 highlighter=_highlighter, 

1566 ) 

1567 ) 

1568 elif isinstance(renderable, Text): 

1569 append_text(renderable) 

1570 elif isinstance(renderable, ConsoleRenderable): 

1571 check_text() 

1572 append(renderable) 

1573 elif is_expandable(renderable): 

1574 check_text() 

1575 from .pretty import Pretty 

1576 

1577 append(Pretty(renderable, highlighter=_highlighter)) 

1578 else: 

1579 append_text(_highlighter(str(renderable))) 

1580 

1581 check_text() 

1582 

1583 if self.style is not None: 

1584 style = self.get_style(self.style) 

1585 renderables = [Styled(renderable, style) for renderable in renderables] 

1586 

1587 return renderables 

1588 

1589 def rule( 

1590 self, 

1591 title: TextType = "", 

1592 *, 

1593 characters: str = "─", 

1594 style: Union[str, Style] = "rule.line", 

1595 align: AlignMethod = "center", 

1596 ) -> None: 

1597 """Draw a line with optional centered title. 

1598 

1599 Args: 

1600 title (str, optional): Text to render over the rule. Defaults to "". 

1601 characters (str, optional): Character(s) to form the line. Defaults to "─". 

1602 style (str, optional): Style of line. Defaults to "rule.line". 

1603 align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center". 

1604 """ 

1605 from .rule import Rule 

1606 

1607 rule = Rule(title=title, characters=characters, style=style, align=align) 

1608 self.print(rule) 

1609 

1610 def control(self, *control: Control) -> None: 

1611 """Insert non-printing control codes. 

1612 

1613 Args: 

1614 control_codes (str): Control codes, such as those that may move the cursor. 

1615 """ 

1616 if not self.is_dumb_terminal: 

1617 with self: 

1618 self._buffer.extend(_control.segment for _control in control) 

1619 

1620 def out( 

1621 self, 

1622 *objects: Any, 

1623 sep: str = " ", 

1624 end: str = "\n", 

1625 style: Optional[Union[str, Style]] = None, 

1626 highlight: Optional[bool] = None, 

1627 ) -> None: 

1628 """Output to the terminal. This is a low-level way of writing to the terminal which unlike 

1629 :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will 

1630 optionally apply highlighting and a basic style. 

1631 

1632 Args: 

1633 sep (str, optional): String to write between print data. Defaults to " ". 

1634 end (str, optional): String to write at end of print data. Defaults to "\\\\n". 

1635 style (Union[str, Style], optional): A style to apply to output. Defaults to None. 

1636 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use 

1637 console default. Defaults to ``None``. 

1638 """ 

1639 raw_output: str = sep.join(str(_object) for _object in objects) 

1640 self.print( 

1641 raw_output, 

1642 style=style, 

1643 highlight=highlight, 

1644 emoji=False, 

1645 markup=False, 

1646 no_wrap=True, 

1647 overflow="ignore", 

1648 crop=False, 

1649 end=end, 

1650 ) 

1651 

1652 def print( 

1653 self, 

1654 *objects: Any, 

1655 sep: str = " ", 

1656 end: str = "\n", 

1657 style: Optional[Union[str, Style]] = None, 

1658 justify: Optional[JustifyMethod] = None, 

1659 overflow: Optional[OverflowMethod] = None, 

1660 no_wrap: Optional[bool] = None, 

1661 emoji: Optional[bool] = None, 

1662 markup: Optional[bool] = None, 

1663 highlight: Optional[bool] = None, 

1664 width: Optional[int] = None, 

1665 height: Optional[int] = None, 

1666 crop: bool = True, 

1667 soft_wrap: Optional[bool] = None, 

1668 new_line_start: bool = False, 

1669 ) -> None: 

1670 """Print to the console. 

1671 

1672 Args: 

1673 objects (positional args): Objects to log to the terminal. 

1674 sep (str, optional): String to write between print data. Defaults to " ". 

1675 end (str, optional): String to write at end of print data. Defaults to "\\\\n". 

1676 style (Union[str, Style], optional): A style to apply to output. Defaults to None. 

1677 justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``. 

1678 overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None. 

1679 no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None. 

1680 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``. 

1681 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``. 

1682 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``. 

1683 width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``. 

1684 crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True. 

1685 soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for 

1686 Console default. Defaults to ``None``. 

1687 new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``. 

1688 """ 

1689 if not objects: 

1690 if end == "\n": 

1691 objects = (NewLine(),) 

1692 else: 

1693 objects = ("",) 

1694 

1695 if soft_wrap is None: 

1696 soft_wrap = self.soft_wrap 

1697 if soft_wrap: 

1698 if no_wrap is None: 

1699 no_wrap = True 

1700 if overflow is None: 

1701 overflow = "ignore" 

1702 crop = False 

1703 render_hooks = self._render_hooks[:] 

1704 with self: 

1705 renderables = self._collect_renderables( 

1706 objects, 

1707 sep, 

1708 end, 

1709 justify=justify, 

1710 emoji=emoji, 

1711 markup=markup, 

1712 highlight=highlight, 

1713 ) 

1714 for hook in render_hooks: 

1715 renderables = hook.process_renderables(renderables) 

1716 render_options = self.options.update( 

1717 justify=justify, 

1718 overflow=overflow, 

1719 width=min(width, self.width) if width is not None else NO_CHANGE, 

1720 height=height, 

1721 no_wrap=no_wrap, 

1722 markup=markup, 

1723 highlight=highlight, 

1724 ) 

1725 

1726 new_segments: List[Segment] = [] 

1727 extend = new_segments.extend 

1728 render = self.render 

1729 if style is None: 

1730 for renderable in renderables: 

1731 extend(render(renderable, render_options)) 

1732 else: 

1733 render_style = self.get_style(style) 

1734 new_line = Segment.line() 

1735 for renderable in renderables: 

1736 for line, add_new_line in Segment.split_lines_terminator( 

1737 render(renderable, render_options) 

1738 ): 

1739 extend(Segment.apply_style(line, render_style)) 

1740 if add_new_line: 

1741 new_segments.append(new_line) 

1742 

1743 if new_line_start: 

1744 if ( 

1745 len("".join(segment.text for segment in new_segments).splitlines()) 

1746 > 1 

1747 ): 

1748 new_segments.insert(0, Segment.line()) 

1749 if crop: 

1750 buffer_extend = self._buffer.extend 

1751 for line in Segment.split_and_crop_lines( 

1752 new_segments, self.width, pad=False 

1753 ): 

1754 buffer_extend(line) 

1755 else: 

1756 self._buffer.extend(new_segments) 

1757 

1758 def print_json( 

1759 self, 

1760 json: Optional[str] = None, 

1761 *, 

1762 data: Any = None, 

1763 indent: Union[None, int, str] = 2, 

1764 highlight: bool = True, 

1765 skip_keys: bool = False, 

1766 ensure_ascii: bool = False, 

1767 check_circular: bool = True, 

1768 allow_nan: bool = True, 

1769 default: Optional[Callable[[Any], Any]] = None, 

1770 sort_keys: bool = False, 

1771 ) -> None: 

1772 """Pretty prints JSON. Output will be valid JSON. 

1773 

1774 Args: 

1775 json (Optional[str]): A string containing JSON. 

1776 data (Any): If json is not supplied, then encode this data. 

1777 indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2. 

1778 highlight (bool, optional): Enable highlighting of output: Defaults to True. 

1779 skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. 

1780 ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. 

1781 check_circular (bool, optional): Check for circular references. Defaults to True. 

1782 allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. 

1783 default (Callable, optional): A callable that converts values that can not be encoded 

1784 in to something that can be JSON encoded. Defaults to None. 

1785 sort_keys (bool, optional): Sort dictionary keys. Defaults to False. 

1786 """ 

1787 from rich.json import JSON 

1788 

1789 if json is None: 

1790 json_renderable = JSON.from_data( 

1791 data, 

1792 indent=indent, 

1793 highlight=highlight, 

1794 skip_keys=skip_keys, 

1795 ensure_ascii=ensure_ascii, 

1796 check_circular=check_circular, 

1797 allow_nan=allow_nan, 

1798 default=default, 

1799 sort_keys=sort_keys, 

1800 ) 

1801 else: 

1802 if not isinstance(json, str): 

1803 raise TypeError( 

1804 f"json must be str. Did you mean print_json(data={json!r}) ?" 

1805 ) 

1806 json_renderable = JSON( 

1807 json, 

1808 indent=indent, 

1809 highlight=highlight, 

1810 skip_keys=skip_keys, 

1811 ensure_ascii=ensure_ascii, 

1812 check_circular=check_circular, 

1813 allow_nan=allow_nan, 

1814 default=default, 

1815 sort_keys=sort_keys, 

1816 ) 

1817 self.print(json_renderable, soft_wrap=True) 

1818 

1819 def update_screen( 

1820 self, 

1821 renderable: RenderableType, 

1822 *, 

1823 region: Optional[Region] = None, 

1824 options: Optional[ConsoleOptions] = None, 

1825 ) -> None: 

1826 """Update the screen at a given offset. 

1827 

1828 Args: 

1829 renderable (RenderableType): A Rich renderable. 

1830 region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None. 

1831 x (int, optional): x offset. Defaults to 0. 

1832 y (int, optional): y offset. Defaults to 0. 

1833 

1834 Raises: 

1835 errors.NoAltScreen: If the Console isn't in alt screen mode. 

1836 

1837 """ 

1838 if not self.is_alt_screen: 

1839 raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") 

1840 render_options = options or self.options 

1841 if region is None: 

1842 x = y = 0 

1843 render_options = render_options.update_dimensions( 

1844 render_options.max_width, render_options.height or self.height 

1845 ) 

1846 else: 

1847 x, y, width, height = region 

1848 render_options = render_options.update_dimensions(width, height) 

1849 

1850 lines = self.render_lines(renderable, options=render_options) 

1851 self.update_screen_lines(lines, x, y) 

1852 

1853 def update_screen_lines( 

1854 self, lines: List[List[Segment]], x: int = 0, y: int = 0 

1855 ) -> None: 

1856 """Update lines of the screen at a given offset. 

1857 

1858 Args: 

1859 lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`). 

1860 x (int, optional): x offset (column no). Defaults to 0. 

1861 y (int, optional): y offset (column no). Defaults to 0. 

1862 

1863 Raises: 

1864 errors.NoAltScreen: If the Console isn't in alt screen mode. 

1865 """ 

1866 if not self.is_alt_screen: 

1867 raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") 

1868 screen_update = ScreenUpdate(lines, x, y) 

1869 segments = self.render(screen_update) 

1870 self._buffer.extend(segments) 

1871 self._check_buffer() 

1872 

1873 def print_exception( 

1874 self, 

1875 *, 

1876 width: Optional[int] = 100, 

1877 extra_lines: int = 3, 

1878 theme: Optional[str] = None, 

1879 word_wrap: bool = False, 

1880 show_locals: bool = False, 

1881 suppress: Iterable[Union[str, ModuleType]] = (), 

1882 max_frames: int = 100, 

1883 ) -> None: 

1884 """Prints a rich render of the last exception and traceback. 

1885 

1886 Args: 

1887 width (Optional[int], optional): Number of characters used to render code. Defaults to 100. 

1888 extra_lines (int, optional): Additional lines of code to render. Defaults to 3. 

1889 theme (str, optional): Override pygments theme used in traceback 

1890 word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. 

1891 show_locals (bool, optional): Enable display of local variables. Defaults to False. 

1892 suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. 

1893 max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. 

1894 """ 

1895 from .traceback import Traceback 

1896 

1897 traceback = Traceback( 

1898 width=width, 

1899 extra_lines=extra_lines, 

1900 theme=theme, 

1901 word_wrap=word_wrap, 

1902 show_locals=show_locals, 

1903 suppress=suppress, 

1904 max_frames=max_frames, 

1905 ) 

1906 self.print(traceback) 

1907 

1908 @staticmethod 

1909 def _caller_frame_info( 

1910 offset: int, currentframe: Optional[Callable[[], Optional[FrameType]]] = None 

1911 ) -> Tuple[str, int, Dict[str, Any]]: 

1912 """Get caller frame information. 

1913 

1914 Args: 

1915 offset (int): the caller offset within the current frame stack. 

1916 currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to 

1917 retrieve the current frame. Defaults to None, which will use ``inspect.currentframe()``. 

1918 

1919 Returns: 

1920 Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and 

1921 the dictionary of local variables associated with the caller frame. 

1922 

1923 Raises: 

1924 RuntimeError: If the stack offset is invalid. 

1925 """ 

1926 # Ignore the frame of this local helper 

1927 offset += 1 

1928 

1929 if currentframe is None: 

1930 import inspect 

1931 

1932 frame = inspect.currentframe() 

1933 else: 

1934 frame = currentframe() 

1935 if frame is not None: 

1936 while offset and frame is not None: 

1937 frame = frame.f_back 

1938 offset -= 1 

1939 assert frame is not None 

1940 return frame.f_code.co_filename, frame.f_lineno, frame.f_locals 

1941 else: 

1942 from inspect import stack 

1943 

1944 frame_info = stack()[offset] 

1945 return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals 

1946 

1947 def log( 

1948 self, 

1949 *objects: Any, 

1950 sep: str = " ", 

1951 end: str = "\n", 

1952 style: Optional[Union[str, Style]] = None, 

1953 justify: Optional[JustifyMethod] = None, 

1954 emoji: Optional[bool] = None, 

1955 markup: Optional[bool] = None, 

1956 highlight: Optional[bool] = None, 

1957 log_locals: bool = False, 

1958 _stack_offset: int = 1, 

1959 ) -> None: 

1960 """Log rich content to the terminal. 

1961 

1962 Args: 

1963 objects (positional args): Objects to log to the terminal. 

1964 sep (str, optional): String to write between print data. Defaults to " ". 

1965 end (str, optional): String to write at end of print data. Defaults to "\\\\n". 

1966 style (Union[str, Style], optional): A style to apply to output. Defaults to None. 

1967 justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. 

1968 emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None. 

1969 markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None. 

1970 highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None. 

1971 log_locals (bool, optional): Boolean to enable logging of locals where ``log()`` 

1972 was called. Defaults to False. 

1973 _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1. 

1974 """ 

1975 if not objects: 

1976 objects = (NewLine(),) 

1977 

1978 render_hooks = self._render_hooks[:] 

1979 

1980 with self: 

1981 renderables = self._collect_renderables( 

1982 objects, 

1983 sep, 

1984 end, 

1985 justify=justify, 

1986 emoji=emoji, 

1987 markup=markup, 

1988 highlight=highlight, 

1989 ) 

1990 if style is not None: 

1991 renderables = [Styled(renderable, style) for renderable in renderables] 

1992 

1993 filename, line_no, locals = self._caller_frame_info(_stack_offset) 

1994 link_path = None if filename.startswith("<") else os.path.abspath(filename) 

1995 path = filename.rpartition(os.sep)[-1] 

1996 if log_locals: 

1997 from .scope import render_scope 

1998 

1999 locals_map = { 

2000 key: value 

2001 for key, value in locals.items() 

2002 if not key.startswith("__") 

2003 } 

2004 renderables.append(render_scope(locals_map, title="[i]locals")) 

2005 

2006 renderables = [ 

2007 self._log_render( 

2008 self, 

2009 renderables, 

2010 log_time=self.get_datetime(), 

2011 path=path, 

2012 line_no=line_no, 

2013 link_path=link_path, 

2014 ) 

2015 ] 

2016 for hook in render_hooks: 

2017 renderables = hook.process_renderables(renderables) 

2018 new_segments: List[Segment] = [] 

2019 extend = new_segments.extend 

2020 render = self.render 

2021 render_options = self.options 

2022 for renderable in renderables: 

2023 extend(render(renderable, render_options)) 

2024 buffer_extend = self._buffer.extend 

2025 for line in Segment.split_and_crop_lines( 

2026 new_segments, self.width, pad=False 

2027 ): 

2028 buffer_extend(line) 

2029 

2030 def on_broken_pipe(self) -> None: 

2031 """This function is called when a `BrokenPipeError` is raised. 

2032 

2033 This can occur when piping Textual output in Linux and macOS. 

2034 The default implementation is to exit the app, but you could implement 

2035 this method in a subclass to change the behavior. 

2036 

2037 See https://docs.python.org/3/library/signal.html#note-on-sigpipe for details. 

2038 """ 

2039 self.quiet = True 

2040 devnull = os.open(os.devnull, os.O_WRONLY) 

2041 os.dup2(devnull, sys.stdout.fileno()) 

2042 raise SystemExit(1) 

2043 

2044 def _check_buffer(self) -> None: 

2045 """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False) 

2046 Rendering is supported on Windows, Unix and Jupyter environments. For 

2047 legacy Windows consoles, the win32 API is called directly. 

2048 This method will also record what it renders if recording is enabled via Console.record. 

2049 """ 

2050 if self.quiet: 

2051 del self._buffer[:] 

2052 return 

2053 

2054 try: 

2055 self._write_buffer() 

2056 except BrokenPipeError: 

2057 self.on_broken_pipe() 

2058 

2059 def _write_buffer(self) -> None: 

2060 """Write the buffer to the output file.""" 

2061 

2062 with self._lock: 

2063 if self.record and not self._buffer_index: 

2064 with self._record_buffer_lock: 

2065 self._record_buffer.extend(self._buffer[:]) 

2066 

2067 if self._buffer_index == 0: 

2068 if self.is_jupyter: # pragma: no cover 

2069 from .jupyter import display 

2070 

2071 display(self._buffer, self._render_buffer(self._buffer[:])) 

2072 del self._buffer[:] 

2073 else: 

2074 if WINDOWS: 

2075 use_legacy_windows_render = False 

2076 if self.legacy_windows: 

2077 fileno = get_fileno(self.file) 

2078 if fileno is not None: 

2079 use_legacy_windows_render = ( 

2080 fileno in _STD_STREAMS_OUTPUT 

2081 ) 

2082 

2083 if use_legacy_windows_render: 

2084 from rich._win32_console import LegacyWindowsTerm 

2085 from rich._windows_renderer import legacy_windows_render 

2086 

2087 buffer = self._buffer[:] 

2088 if self.no_color and self._color_system: 

2089 buffer = list(Segment.remove_color(buffer)) 

2090 

2091 legacy_windows_render(buffer, LegacyWindowsTerm(self.file)) 

2092 else: 

2093 # Either a non-std stream on legacy Windows, or modern Windows. 

2094 text = self._render_buffer(self._buffer[:]) 

2095 # https://bugs.python.org/issue37871 

2096 # https://github.com/python/cpython/issues/82052 

2097 # We need to avoid writing more than 32Kb in a single write, due to the above bug 

2098 write = self.file.write 

2099 # Worse case scenario, every character is 4 bytes of utf-8 

2100 MAX_WRITE = 32 * 1024 // 4 

2101 try: 

2102 if len(text) <= MAX_WRITE: 

2103 write(text) 

2104 else: 

2105 batch: List[str] = [] 

2106 batch_append = batch.append 

2107 size = 0 

2108 for line in text.splitlines(True): 

2109 if size + len(line) > MAX_WRITE and batch: 

2110 write("".join(batch)) 

2111 batch.clear() 

2112 size = 0 

2113 batch_append(line) 

2114 size += len(line) 

2115 if batch: 

2116 write("".join(batch)) 

2117 batch.clear() 

2118 except UnicodeEncodeError as error: 

2119 error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" 

2120 raise 

2121 else: 

2122 text = self._render_buffer(self._buffer[:]) 

2123 try: 

2124 self.file.write(text) 

2125 except UnicodeEncodeError as error: 

2126 error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" 

2127 raise 

2128 

2129 self.file.flush() 

2130 del self._buffer[:] 

2131 

2132 def _render_buffer(self, buffer: Iterable[Segment]) -> str: 

2133 """Render buffered output, and clear buffer.""" 

2134 output: List[str] = [] 

2135 append = output.append 

2136 color_system = self._color_system 

2137 legacy_windows = self.legacy_windows 

2138 not_terminal = not self.is_terminal 

2139 if self.no_color and color_system: 

2140 buffer = Segment.remove_color(buffer) 

2141 for text, style, control in buffer: 

2142 if style: 

2143 append( 

2144 style.render( 

2145 text, 

2146 color_system=color_system, 

2147 legacy_windows=legacy_windows, 

2148 ) 

2149 ) 

2150 elif not (not_terminal and control): 

2151 append(text) 

2152 

2153 rendered = "".join(output) 

2154 return rendered 

2155 

2156 def input( 

2157 self, 

2158 prompt: TextType = "", 

2159 *, 

2160 markup: bool = True, 

2161 emoji: bool = True, 

2162 password: bool = False, 

2163 stream: Optional[TextIO] = None, 

2164 ) -> str: 

2165 """Displays a prompt and waits for input from the user. The prompt may contain color / style. 

2166 

2167 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. 

2168 

2169 Args: 

2170 prompt (Union[str, Text]): Text to render in the prompt. 

2171 markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True. 

2172 emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True. 

2173 password: (bool, optional): Hide typed text. Defaults to False. 

2174 stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None. 

2175 

2176 Returns: 

2177 str: Text read from stdin. 

2178 """ 

2179 if prompt: 

2180 self.print(prompt, markup=markup, emoji=emoji, end="") 

2181 if password: 

2182 import getpass as _getpass_mod 

2183 

2184 result = _getpass_mod.getpass("", stream=stream) 

2185 else: 

2186 if stream: 

2187 result = stream.readline() 

2188 else: 

2189 result = input() 

2190 return result 

2191 

2192 def export_text(self, *, clear: bool = True, styles: bool = False) -> str: 

2193 """Generate text from console contents (requires record=True argument in constructor). 

2194 

2195 Args: 

2196 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. 

2197 styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text. 

2198 Defaults to ``False``. 

2199 

2200 Returns: 

2201 str: String containing console contents. 

2202 

2203 """ 

2204 assert ( 

2205 self.record 

2206 ), "To export console contents set record=True in the constructor or instance" 

2207 

2208 with self._record_buffer_lock: 

2209 if styles: 

2210 text = "".join( 

2211 (style.render(text) if style else text) 

2212 for text, style, _ in self._record_buffer 

2213 ) 

2214 else: 

2215 text = "".join( 

2216 segment.text 

2217 for segment in self._record_buffer 

2218 if not segment.control 

2219 ) 

2220 if clear: 

2221 del self._record_buffer[:] 

2222 return text 

2223 

2224 def save_text( 

2225 self, 

2226 path: Union[str, PathLike[str]], 

2227 *, 

2228 clear: bool = True, 

2229 styles: bool = False, 

2230 ) -> None: 

2231 """Generate text from console and save to a given location (requires record=True argument in constructor). 

2232 

2233 Args: 

2234 path (Union[str, PathLike[str]]): Path to write text files. 

2235 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. 

2236 styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text. 

2237 Defaults to ``False``. 

2238 

2239 """ 

2240 text = self.export_text(clear=clear, styles=styles) 

2241 with open(path, "w", encoding="utf-8") as write_file: 

2242 write_file.write(text) 

2243 

2244 def export_html( 

2245 self, 

2246 *, 

2247 theme: Optional[TerminalTheme] = None, 

2248 clear: bool = True, 

2249 code_format: Optional[str] = None, 

2250 inline_styles: bool = False, 

2251 ) -> str: 

2252 """Generate HTML from console contents (requires record=True argument in constructor). 

2253 

2254 Args: 

2255 theme (TerminalTheme, optional): TerminalTheme object containing console colors. 

2256 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. 

2257 code_format (str, optional): Format string to render HTML. In addition to '{foreground}', 

2258 '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. 

2259 inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files 

2260 larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. 

2261 Defaults to False. 

2262 

2263 Returns: 

2264 str: String containing console contents as HTML. 

2265 """ 

2266 from html import escape 

2267 

2268 assert ( 

2269 self.record 

2270 ), "To export console contents set record=True in the constructor or instance" 

2271 fragments: List[str] = [] 

2272 append = fragments.append 

2273 _theme = theme or DEFAULT_TERMINAL_THEME 

2274 stylesheet = "" 

2275 

2276 render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format 

2277 

2278 with self._record_buffer_lock: 

2279 if inline_styles: 

2280 for text, style, _ in Segment.filter_control( 

2281 Segment.simplify(self._record_buffer) 

2282 ): 

2283 text = escape(text) 

2284 if style: 

2285 rule = style.get_html_style(_theme) 

2286 if style.link: 

2287 text = f'<a href="{style.link}">{text}</a>' 

2288 text = f'<span style="{rule}">{text}</span>' if rule else text 

2289 append(text) 

2290 else: 

2291 styles: Dict[str, int] = {} 

2292 for text, style, _ in Segment.filter_control( 

2293 Segment.simplify(self._record_buffer) 

2294 ): 

2295 text = escape(text) 

2296 if style: 

2297 rule = style.get_html_style(_theme) 

2298 style_number = styles.setdefault(rule, len(styles) + 1) 

2299 if style.link: 

2300 text = f'<a class="r{style_number}" href="{style.link}">{text}</a>' 

2301 else: 

2302 text = f'<span class="r{style_number}">{text}</span>' 

2303 append(text) 

2304 stylesheet_rules: List[str] = [] 

2305 stylesheet_append = stylesheet_rules.append 

2306 for style_rule, style_number in styles.items(): 

2307 if style_rule: 

2308 stylesheet_append(f".r{style_number} {{{style_rule}}}") 

2309 stylesheet = "\n".join(stylesheet_rules) 

2310 

2311 rendered_code = render_code_format.format( 

2312 code="".join(fragments), 

2313 stylesheet=stylesheet, 

2314 foreground=_theme.foreground_color.hex, 

2315 background=_theme.background_color.hex, 

2316 ) 

2317 if clear: 

2318 del self._record_buffer[:] 

2319 return rendered_code 

2320 

2321 def save_html( 

2322 self, 

2323 path: Union[str, PathLike[str]], 

2324 *, 

2325 theme: Optional[TerminalTheme] = None, 

2326 clear: bool = True, 

2327 code_format: str = CONSOLE_HTML_FORMAT, 

2328 inline_styles: bool = False, 

2329 ) -> None: 

2330 """Generate HTML from console contents and write to a file (requires record=True argument in constructor). 

2331 

2332 Args: 

2333 path (Union[str, PathLike[str]]): Path to write html file. 

2334 theme (TerminalTheme, optional): TerminalTheme object containing console colors. 

2335 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. 

2336 code_format (str, optional): Format string to render HTML. In addition to '{foreground}', 

2337 '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. 

2338 inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files 

2339 larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. 

2340 Defaults to False. 

2341 

2342 """ 

2343 html = self.export_html( 

2344 theme=theme, 

2345 clear=clear, 

2346 code_format=code_format, 

2347 inline_styles=inline_styles, 

2348 ) 

2349 with open(path, "w", encoding="utf-8") as write_file: 

2350 write_file.write(html) 

2351 

2352 def export_svg( 

2353 self, 

2354 *, 

2355 title: str = "Rich", 

2356 theme: Optional[TerminalTheme] = None, 

2357 clear: bool = True, 

2358 code_format: str = CONSOLE_SVG_FORMAT, 

2359 font_aspect_ratio: float = 0.61, 

2360 unique_id: Optional[str] = None, 

2361 ) -> str: 

2362 """ 

2363 Generate an SVG from the console contents (requires record=True in Console constructor). 

2364 

2365 Args: 

2366 title (str, optional): The title of the tab in the output image 

2367 theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal 

2368 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` 

2369 code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables 

2370 into the string in order to form the final SVG output. The default template used and the variables 

2371 injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. 

2372 font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` 

2373 string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). 

2374 If you aren't specifying a different font inside ``code_format``, you probably don't need this. 

2375 unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node 

2376 ids). If not set, this defaults to a computed value based on the recorded content. 

2377 """ 

2378 

2379 import zlib 

2380 from html import escape 

2381 

2382 from rich.cells import cell_len 

2383 

2384 style_cache: Dict[Style, str] = {} 

2385 

2386 def get_svg_style(style: Style) -> str: 

2387 """Convert a Style to CSS rules for SVG.""" 

2388 if style in style_cache: 

2389 return style_cache[style] 

2390 css_rules = [] 

2391 color = ( 

2392 _theme.foreground_color 

2393 if (style.color is None or style.color.is_default) 

2394 else style.color.get_truecolor(_theme) 

2395 ) 

2396 bgcolor = ( 

2397 _theme.background_color 

2398 if (style.bgcolor is None or style.bgcolor.is_default) 

2399 else style.bgcolor.get_truecolor(_theme) 

2400 ) 

2401 if style.reverse: 

2402 color, bgcolor = bgcolor, color 

2403 if style.dim: 

2404 color = blend_rgb(color, bgcolor, 0.4) 

2405 css_rules.append(f"fill: {color.hex}") 

2406 if style.bold: 

2407 css_rules.append("font-weight: bold") 

2408 if style.italic: 

2409 css_rules.append("font-style: italic;") 

2410 if style.underline: 

2411 css_rules.append("text-decoration: underline;") 

2412 if style.strike: 

2413 css_rules.append("text-decoration: line-through;") 

2414 

2415 css = ";".join(css_rules) 

2416 style_cache[style] = css 

2417 return css 

2418 

2419 _theme = theme or SVG_EXPORT_THEME 

2420 

2421 width = self.width 

2422 char_height = 20 

2423 char_width = char_height * font_aspect_ratio 

2424 line_height = char_height * 1.22 

2425 

2426 margin_top = 1 

2427 margin_right = 1 

2428 margin_bottom = 1 

2429 margin_left = 1 

2430 

2431 padding_top = 40 

2432 padding_right = 8 

2433 padding_bottom = 8 

2434 padding_left = 8 

2435 

2436 padding_width = padding_left + padding_right 

2437 padding_height = padding_top + padding_bottom 

2438 margin_width = margin_left + margin_right 

2439 margin_height = margin_top + margin_bottom 

2440 

2441 text_backgrounds: List[str] = [] 

2442 text_group: List[str] = [] 

2443 classes: Dict[str, int] = {} 

2444 style_no = 1 

2445 

2446 def escape_text(text: str) -> str: 

2447 """HTML escape text and replace spaces with nbsp.""" 

2448 return escape(text).replace(" ", "&#160;") 

2449 

2450 def make_tag( 

2451 name: str, content: Optional[str] = None, **attribs: object 

2452 ) -> str: 

2453 """Make a tag from name, content, and attributes.""" 

2454 

2455 def stringify(value: object) -> str: 

2456 if isinstance(value, (float)): 

2457 return format(value, "g") 

2458 return str(value) 

2459 

2460 tag_attribs = " ".join( 

2461 f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"' 

2462 for k, v in attribs.items() 

2463 ) 

2464 return ( 

2465 f"<{name} {tag_attribs}>{content}</{name}>" 

2466 if content 

2467 else f"<{name} {tag_attribs}/>" 

2468 ) 

2469 

2470 with self._record_buffer_lock: 

2471 segments = list(Segment.filter_control(self._record_buffer)) 

2472 if clear: 

2473 self._record_buffer.clear() 

2474 

2475 if unique_id is None: 

2476 unique_id = "terminal-" + str( 

2477 zlib.adler32( 

2478 ("".join(repr(segment) for segment in segments)).encode( 

2479 "utf-8", 

2480 "ignore", 

2481 ) 

2482 + title.encode("utf-8", "ignore") 

2483 ) 

2484 ) 

2485 y = 0 

2486 for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)): 

2487 x = 0 

2488 for text, style, _control in line: 

2489 style = style or Style() 

2490 rules = get_svg_style(style) 

2491 if rules not in classes: 

2492 classes[rules] = style_no 

2493 style_no += 1 

2494 class_name = f"r{classes[rules]}" 

2495 

2496 if style.reverse: 

2497 has_background = True 

2498 background = ( 

2499 _theme.foreground_color.hex 

2500 if style.color is None 

2501 else style.color.get_truecolor(_theme).hex 

2502 ) 

2503 else: 

2504 bgcolor = style.bgcolor 

2505 has_background = bgcolor is not None and not bgcolor.is_default 

2506 background = ( 

2507 _theme.background_color.hex 

2508 if style.bgcolor is None 

2509 else style.bgcolor.get_truecolor(_theme).hex 

2510 ) 

2511 

2512 text_length = cell_len(text) 

2513 if has_background: 

2514 text_backgrounds.append( 

2515 make_tag( 

2516 "rect", 

2517 fill=background, 

2518 x=x * char_width, 

2519 y=y * line_height + 1.5, 

2520 width=char_width * text_length, 

2521 height=line_height + 0.25, 

2522 shape_rendering="crispEdges", 

2523 ) 

2524 ) 

2525 

2526 if text != " " * len(text): 

2527 text_group.append( 

2528 make_tag( 

2529 "text", 

2530 escape_text(text), 

2531 _class=f"{unique_id}-{class_name}", 

2532 x=x * char_width, 

2533 y=y * line_height + char_height, 

2534 textLength=char_width * len(text), 

2535 clip_path=f"url(#{unique_id}-line-{y})", 

2536 ) 

2537 ) 

2538 x += cell_len(text) 

2539 

2540 line_offsets = [line_no * line_height + 1.5 for line_no in range(y)] 

2541 lines = "\n".join( 

2542 f"""<clipPath id="{unique_id}-line-{line_no}"> 

2543 {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)} 

2544 </clipPath>""" 

2545 for line_no, offset in enumerate(line_offsets) 

2546 ) 

2547 

2548 styles = "\n".join( 

2549 f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items() 

2550 ) 

2551 backgrounds = "".join(text_backgrounds) 

2552 matrix = "".join(text_group) 

2553 

2554 terminal_width = ceil(width * char_width + padding_width) 

2555 terminal_height = (y + 1) * line_height + padding_height 

2556 chrome = make_tag( 

2557 "rect", 

2558 fill=_theme.background_color.hex, 

2559 stroke="rgba(255,255,255,0.35)", 

2560 stroke_width="1", 

2561 x=margin_left, 

2562 y=margin_top, 

2563 width=terminal_width, 

2564 height=terminal_height, 

2565 rx=8, 

2566 ) 

2567 

2568 title_color = _theme.foreground_color.hex 

2569 if title: 

2570 chrome += make_tag( 

2571 "text", 

2572 escape_text(title), 

2573 _class=f"{unique_id}-title", 

2574 fill=title_color, 

2575 text_anchor="middle", 

2576 x=terminal_width // 2, 

2577 y=margin_top + char_height + 6, 

2578 ) 

2579 chrome += f""" 

2580 <g transform="translate(26,22)"> 

2581 <circle cx="0" cy="0" r="7" fill="#ff5f57"/> 

2582 <circle cx="22" cy="0" r="7" fill="#febc2e"/> 

2583 <circle cx="44" cy="0" r="7" fill="#28c840"/> 

2584 </g> 

2585 """ 

2586 

2587 svg = code_format.format( 

2588 unique_id=unique_id, 

2589 char_width=char_width, 

2590 char_height=char_height, 

2591 line_height=line_height, 

2592 terminal_width=char_width * width - 1, 

2593 terminal_height=(y + 1) * line_height - 1, 

2594 width=terminal_width + margin_width, 

2595 height=terminal_height + margin_height, 

2596 terminal_x=margin_left + padding_left, 

2597 terminal_y=margin_top + padding_top, 

2598 styles=styles, 

2599 chrome=chrome, 

2600 backgrounds=backgrounds, 

2601 matrix=matrix, 

2602 lines=lines, 

2603 ) 

2604 return svg 

2605 

2606 def save_svg( 

2607 self, 

2608 path: Union[str, PathLike[str]], 

2609 *, 

2610 title: str = "Rich", 

2611 theme: Optional[TerminalTheme] = None, 

2612 clear: bool = True, 

2613 code_format: str = CONSOLE_SVG_FORMAT, 

2614 font_aspect_ratio: float = 0.61, 

2615 unique_id: Optional[str] = None, 

2616 ) -> None: 

2617 """Generate an SVG file from the console contents (requires record=True in Console constructor). 

2618 

2619 Args: 

2620 path (Union[str, PathLike[str]]): The path to write the SVG to. 

2621 title (str, optional): The title of the tab in the output image 

2622 theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal 

2623 clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` 

2624 code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables 

2625 into the string in order to form the final SVG output. The default template used and the variables 

2626 injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. 

2627 font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` 

2628 string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). 

2629 If you aren't specifying a different font inside ``code_format``, you probably don't need this. 

2630 unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node 

2631 ids). If not set, this defaults to a computed value based on the recorded content. 

2632 """ 

2633 svg = self.export_svg( 

2634 title=title, 

2635 theme=theme, 

2636 clear=clear, 

2637 code_format=code_format, 

2638 font_aspect_ratio=font_aspect_ratio, 

2639 unique_id=unique_id, 

2640 ) 

2641 with open(path, "w", encoding="utf-8") as write_file: 

2642 write_file.write(svg) 

2643 

2644 

2645if __name__ == "__main__": # pragma: no cover 

2646 console = Console(record=True) 

2647 

2648 console.log( 

2649 "JSONRPC [i]request[/i]", 

2650 5, 

2651 1.3, 

2652 True, 

2653 False, 

2654 None, 

2655 { 

2656 "jsonrpc": "2.0", 

2657 "method": "subtract", 

2658 "params": {"minuend": 42, "subtrahend": 23}, 

2659 "id": 3, 

2660 }, 

2661 ) 

2662 

2663 console.log("Hello, World!", "{'a': 1}", repr(console)) 

2664 

2665 console.print( 

2666 { 

2667 "name": None, 

2668 "empty": [], 

2669 "quiz": { 

2670 "sport": { 

2671 "answered": True, 

2672 "q1": { 

2673 "question": "Which one is correct team name in NBA?", 

2674 "options": [ 

2675 "New York Bulls", 

2676 "Los Angeles Kings", 

2677 "Golden State Warriors", 

2678 "Huston Rocket", 

2679 ], 

2680 "answer": "Huston Rocket", 

2681 }, 

2682 }, 

2683 "maths": { 

2684 "answered": False, 

2685 "q1": { 

2686 "question": "5 + 7 = ?", 

2687 "options": [10, 11, 12, 13], 

2688 "answer": 12, 

2689 }, 

2690 "q2": { 

2691 "question": "12 - 8 = ?", 

2692 "options": [1, 2, 3, 4], 

2693 "answer": 4, 

2694 }, 

2695 }, 

2696 }, 

2697 } 

2698 )