Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_vendor/rich/console.py: 25%

962 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:48 +0000

1import inspect 

2import os 

3import platform 

4import sys 

5import threading 

6import zlib 

7from abc import ABC, abstractmethod 

8from dataclasses import dataclass, field 

9from datetime import datetime 

10from functools import wraps 

11from getpass import getpass 

12from html import escape 

13from inspect import isclass 

14from itertools import islice 

15from math import ceil 

16from time import monotonic 

17from types import FrameType, ModuleType, TracebackType 

18from typing import ( 

19 IO, 

20 TYPE_CHECKING, 

21 Any, 

22 Callable, 

23 Dict, 

24 Iterable, 

25 List, 

26 Mapping, 

27 NamedTuple, 

28 Optional, 

29 TextIO, 

30 Tuple, 

31 Type, 

32 Union, 

33 cast, 

34) 

35 

36from pip._vendor.rich._null_file import NULL_FILE 

37 

38if sys.version_info >= (3, 8): 

39 from typing import Literal, Protocol, runtime_checkable 

40else: 

41 from pip._vendor.typing_extensions import ( 

42 Literal, 

43 Protocol, 

44 runtime_checkable, 

45 ) # pragma: no cover 

46 

47from . import errors, themes 

48from ._emoji_replace import _emoji_replace 

49from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT 

50from ._fileno import get_fileno 

51from ._log_render import FormatTimeCallable, LogRender 

52from .align import Align, AlignMethod 

53from .color import ColorSystem, blend_rgb 

54from .control import Control 

55from .emoji import EmojiVariant 

56from .highlighter import NullHighlighter, ReprHighlighter 

57from .markup import render as render_markup 

58from .measure import Measurement, measure_renderables 

59from .pager import Pager, SystemPager 

60from .pretty import Pretty, is_expandable 

61from .protocol import rich_cast 

62from .region import Region 

63from .scope import render_scope 

64from .screen import Screen 

65from .segment import Segment 

66from .style import Style, StyleType 

67from .styled import Styled 

68from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme 

69from .text import Text, TextType 

70from .theme import Theme, ThemeStack 

71 

72if TYPE_CHECKING: 

73 from ._windows import WindowsConsoleFeatures 

74 from .live import Live 

75 from .status import Status 

76 

77JUPYTER_DEFAULT_COLUMNS = 115 

78JUPYTER_DEFAULT_LINES = 100 

79WINDOWS = platform.system() == "Windows" 

80 

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

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

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

84 

85 

86class NoChange: 

87 pass 

88 

89 

90NO_CHANGE = NoChange() 

91 

92try: 

93 _STDIN_FILENO = sys.__stdin__.fileno() 

94except Exception: 

95 _STDIN_FILENO = 0 

96try: 

97 _STDOUT_FILENO = sys.__stdout__.fileno() 

98except Exception: 

99 _STDOUT_FILENO = 1 

100try: 

101 _STDERR_FILENO = sys.__stderr__.fileno() 

102except Exception: 

103 _STDERR_FILENO = 2 

104 

105_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO) 

106_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO) 

107 

108 

109_TERM_COLORS = { 

110 "kitty": ColorSystem.EIGHT_BIT, 

111 "256color": ColorSystem.EIGHT_BIT, 

112 "16color": ColorSystem.STANDARD, 

113} 

114 

115 

116class ConsoleDimensions(NamedTuple): 

117 """Size of the terminal.""" 

118 

119 width: int 

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

121 height: int 

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

123 

124 

125@dataclass 

126class ConsoleOptions: 

127 """Options for __rich_console__ method.""" 

128 

129 size: ConsoleDimensions 

130 """Size of console.""" 

131 legacy_windows: bool 

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

133 min_width: int 

134 """Minimum width of renderable.""" 

135 max_width: int 

136 """Maximum width of renderable.""" 

137 is_terminal: bool 

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

139 encoding: str 

140 """Encoding of terminal.""" 

141 max_height: int 

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

143 justify: Optional[JustifyMethod] = None 

144 """Justify value override for renderable.""" 

145 overflow: Optional[OverflowMethod] = None 

146 """Overflow value override for renderable.""" 

147 no_wrap: Optional[bool] = False 

148 """Disable wrapping for text.""" 

149 highlight: Optional[bool] = None 

150 """Highlight override for render_str.""" 

151 markup: Optional[bool] = None 

152 """Enable markup when rendering strings.""" 

153 height: Optional[int] = None 

154 

155 @property 

156 def ascii_only(self) -> bool: 

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

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

159 

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

161 """Return a copy of the options. 

162 

163 Returns: 

164 ConsoleOptions: a copy of self. 

165 """ 

166 options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions) 

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

168 return options 

169 

170 def update( 

171 self, 

172 *, 

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

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

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

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

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

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

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

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

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

182 ) -> "ConsoleOptions": 

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

184 options = self.copy() 

185 if not isinstance(width, NoChange): 

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

187 if not isinstance(min_width, NoChange): 

188 options.min_width = min_width 

189 if not isinstance(max_width, NoChange): 

190 options.max_width = max_width 

191 if not isinstance(justify, NoChange): 

192 options.justify = justify 

193 if not isinstance(overflow, NoChange): 

194 options.overflow = overflow 

195 if not isinstance(no_wrap, NoChange): 

196 options.no_wrap = no_wrap 

197 if not isinstance(highlight, NoChange): 

198 options.highlight = highlight 

199 if not isinstance(markup, NoChange): 

200 options.markup = markup 

201 if not isinstance(height, NoChange): 

202 if height is not None: 

203 options.max_height = height 

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

205 return options 

206 

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

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

209 

210 Args: 

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

212 

213 Returns: 

214 ~ConsoleOptions: New console options instance. 

215 """ 

216 options = self.copy() 

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

218 return options 

219 

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

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

222 

223 Args: 

224 height (int): New height 

225 

226 Returns: 

227 ~ConsoleOptions: New Console options instance. 

228 """ 

229 options = self.copy() 

230 options.max_height = options.height = height 

231 return options 

232 

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

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

235 

236 Returns: 

237 ~ConsoleOptions: New console options instance. 

238 """ 

239 options = self.copy() 

240 options.height = None 

241 return options 

242 

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

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

245 

246 Args: 

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

248 height (int): New height. 

249 

250 Returns: 

251 ~ConsoleOptions: New console options instance. 

252 """ 

253 options = self.copy() 

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

255 options.height = options.max_height = height 

256 return options 

257 

258 

259@runtime_checkable 

260class RichCast(Protocol): 

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

262 

263 def __rich__( 

264 self, 

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

266 ... 

267 

268 

269@runtime_checkable 

270class ConsoleRenderable(Protocol): 

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

272 

273 def __rich_console__( 

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

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

276 ... 

277 

278 

279# A type that may be rendered by Console. 

280RenderableType = Union[ConsoleRenderable, RichCast, str] 

281 

282# The result of calling a __rich_console__ method. 

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

284 

285_null_highlighter = NullHighlighter() 

286 

287 

288class CaptureError(Exception): 

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

290 

291 

292class NewLine: 

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

294 

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

296 self.count = count 

297 

298 def __rich_console__( 

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

300 ) -> Iterable[Segment]: 

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

302 

303 

304class ScreenUpdate: 

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

306 

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

308 self._lines = lines 

309 self.x = x 

310 self.y = y 

311 

312 def __rich_console__( 

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

314 ) -> RenderResult: 

315 x = self.x 

316 move_to = Control.move_to 

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

318 yield move_to(x, offset) 

319 yield from line 

320 

321 

322class Capture: 

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

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

325 

326 Args: 

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

328 """ 

329 

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

331 self._console = console 

332 self._result: Optional[str] = None 

333 

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

335 self._console.begin_capture() 

336 return self 

337 

338 def __exit__( 

339 self, 

340 exc_type: Optional[Type[BaseException]], 

341 exc_val: Optional[BaseException], 

342 exc_tb: Optional[TracebackType], 

343 ) -> None: 

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

345 

346 def get(self) -> str: 

347 """Get the result of the capture.""" 

348 if self._result is None: 

349 raise CaptureError( 

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

351 ) 

352 return self._result 

353 

354 

355class ThemeContext: 

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

357 

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

359 self.console = console 

360 self.theme = theme 

361 self.inherit = inherit 

362 

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

364 self.console.push_theme(self.theme) 

365 return self 

366 

367 def __exit__( 

368 self, 

369 exc_type: Optional[Type[BaseException]], 

370 exc_val: Optional[BaseException], 

371 exc_tb: Optional[TracebackType], 

372 ) -> None: 

373 self.console.pop_theme() 

374 

375 

376class PagerContext: 

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

378 

379 def __init__( 

380 self, 

381 console: "Console", 

382 pager: Optional[Pager] = None, 

383 styles: bool = False, 

384 links: bool = False, 

385 ) -> None: 

386 self._console = console 

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

388 self.styles = styles 

389 self.links = links 

390 

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

392 self._console._enter_buffer() 

393 return self 

394 

395 def __exit__( 

396 self, 

397 exc_type: Optional[Type[BaseException]], 

398 exc_val: Optional[BaseException], 

399 exc_tb: Optional[TracebackType], 

400 ) -> None: 

401 if exc_type is None: 

402 with self._console._lock: 

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

404 del self._console._buffer[:] 

405 segments: Iterable[Segment] = buffer 

406 if not self.styles: 

407 segments = Segment.strip_styles(segments) 

408 elif not self.links: 

409 segments = Segment.strip_links(segments) 

410 content = self._console._render_buffer(segments) 

411 self.pager.show(content) 

412 self._console._exit_buffer() 

413 

414 

415class ScreenContext: 

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

417 

418 def __init__( 

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

420 ) -> None: 

421 self.console = console 

422 self.hide_cursor = hide_cursor 

423 self.screen = Screen(style=style) 

424 self._changed = False 

425 

426 def update( 

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

428 ) -> None: 

429 """Update the screen. 

430 

431 Args: 

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

433 or None for no change. Defaults to None. 

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

435 """ 

436 if renderables: 

437 self.screen.renderable = ( 

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

439 ) 

440 if style is not None: 

441 self.screen.style = style 

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

443 

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

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

446 if self._changed and self.hide_cursor: 

447 self.console.show_cursor(False) 

448 return self 

449 

450 def __exit__( 

451 self, 

452 exc_type: Optional[Type[BaseException]], 

453 exc_val: Optional[BaseException], 

454 exc_tb: Optional[TracebackType], 

455 ) -> None: 

456 if self._changed: 

457 self.console.set_alt_screen(False) 

458 if self.hide_cursor: 

459 self.console.show_cursor(True) 

460 

461 

462class Group: 

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

464 

465 Args: 

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

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

468 """ 

469 

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

471 self._renderables = renderables 

472 self.fit = fit 

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

474 

475 @property 

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

477 if self._render is None: 

478 self._render = list(self._renderables) 

479 return self._render 

480 

481 def __rich_measure__( 

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

483 ) -> "Measurement": 

484 if self.fit: 

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

486 else: 

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

488 

489 def __rich_console__( 

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

491 ) -> RenderResult: 

492 yield from self.renderables 

493 

494 

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

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

497 

498 Args: 

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

500 """ 

501 

502 def decorator( 

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

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

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

506 

507 @wraps(method) 

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

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

510 return Group(*renderables, fit=fit) 

511 

512 return _replace 

513 

514 return decorator 

515 

516 

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

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

519 try: 

520 get_ipython # type: ignore[name-defined] 

521 except NameError: 

522 return False 

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

524 shell = ipython.__class__.__name__ 

525 if ( 

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

527 or os.getenv("DATABRICKS_RUNTIME_VERSION") 

528 or shell == "ZMQInteractiveShell" 

529 ): 

530 return True # Jupyter notebook or qtconsole 

531 elif shell == "TerminalInteractiveShell": 

532 return False # Terminal running IPython 

533 else: 

534 return False # Other type (?) 

535 

536 

537COLOR_SYSTEMS = { 

538 "standard": ColorSystem.STANDARD, 

539 "256": ColorSystem.EIGHT_BIT, 

540 "truecolor": ColorSystem.TRUECOLOR, 

541 "windows": ColorSystem.WINDOWS, 

542} 

543 

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

545 

546 

547@dataclass 

548class ConsoleThreadLocals(threading.local): 

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

550 

551 theme_stack: ThemeStack 

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

553 buffer_index: int = 0 

554 

555 

556class RenderHook(ABC): 

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

558 

559 @abstractmethod 

560 def process_renderables( 

561 self, renderables: List[ConsoleRenderable] 

562 ) -> List[ConsoleRenderable]: 

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

564 

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

566 

567 Args: 

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

569 

570 Returns: 

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

572 """ 

573 

574 

575_windows_console_features: Optional["WindowsConsoleFeatures"] = None 

576 

577 

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

579 global _windows_console_features 

580 if _windows_console_features is not None: 

581 return _windows_console_features 

582 from ._windows import get_windows_console_features 

583 

584 _windows_console_features = get_windows_console_features() 

585 return _windows_console_features 

586 

587 

588def detect_legacy_windows() -> bool: 

589 """Detect legacy Windows.""" 

590 return WINDOWS and not get_windows_console_features().vt 

591 

592 

593class Console: 

594 """A high level console interface. 

595 

596 Args: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

620 log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ". 

621 highlighter (HighlighterType, optional): Default highlighter. 

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

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

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

625 or None for datetime.now. 

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

627 """ 

628 

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

630 

631 def __init__( 

632 self, 

633 *, 

634 color_system: Optional[ 

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

636 ] = "auto", 

637 force_terminal: Optional[bool] = None, 

638 force_jupyter: Optional[bool] = None, 

639 force_interactive: Optional[bool] = None, 

640 soft_wrap: bool = False, 

641 theme: Optional[Theme] = None, 

642 stderr: bool = False, 

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

644 quiet: bool = False, 

645 width: Optional[int] = None, 

646 height: Optional[int] = None, 

647 style: Optional[StyleType] = None, 

648 no_color: Optional[bool] = None, 

649 tab_size: int = 8, 

650 record: bool = False, 

651 markup: bool = True, 

652 emoji: bool = True, 

653 emoji_variant: Optional[EmojiVariant] = None, 

654 highlight: bool = True, 

655 log_time: bool = True, 

656 log_path: bool = True, 

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

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

659 legacy_windows: Optional[bool] = None, 

660 safe_box: bool = True, 

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

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

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

664 ): 

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

666 if _environ is not None: 

667 self._environ = _environ 

668 

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

670 if self.is_jupyter: 

671 if width is None: 

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

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

674 width = int(jupyter_columns) 

675 else: 

676 width = JUPYTER_DEFAULT_COLUMNS 

677 if height is None: 

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

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

680 height = int(jupyter_lines) 

681 else: 

682 height = JUPYTER_DEFAULT_LINES 

683 

684 self.tab_size = tab_size 

685 self.record = record 

686 self._markup = markup 

687 self._emoji = emoji 

688 self._emoji_variant: Optional[EmojiVariant] = emoji_variant 

689 self._highlight = highlight 

690 self.legacy_windows: bool = ( 

691 (detect_legacy_windows() and not self.is_jupyter) 

692 if legacy_windows is None 

693 else legacy_windows 

694 ) 

695 

696 if width is None: 

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

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

699 width = int(columns) - self.legacy_windows 

700 if height is None: 

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

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

703 height = int(lines) 

704 

705 self.soft_wrap = soft_wrap 

706 self._width = width 

707 self._height = height 

708 

709 self._color_system: Optional[ColorSystem] 

710 

711 self._force_terminal = None 

712 if force_terminal is not None: 

713 self._force_terminal = force_terminal 

714 

715 self._file = file 

716 self.quiet = quiet 

717 self.stderr = stderr 

718 

719 if color_system is None: 

720 self._color_system = None 

721 elif color_system == "auto": 

722 self._color_system = self._detect_color_system() 

723 else: 

724 self._color_system = COLOR_SYSTEMS[color_system] 

725 

726 self._lock = threading.RLock() 

727 self._log_render = LogRender( 

728 show_time=log_time, 

729 show_path=log_path, 

730 time_format=log_time_format, 

731 ) 

732 self.highlighter: HighlighterType = highlighter or _null_highlighter 

733 self.safe_box = safe_box 

734 self.get_datetime = get_datetime or datetime.now 

735 self.get_time = get_time or monotonic 

736 self.style = style 

737 self.no_color = ( 

738 no_color if no_color is not None else "NO_COLOR" in self._environ 

739 ) 

740 self.is_interactive = ( 

741 (self.is_terminal and not self.is_dumb_terminal) 

742 if force_interactive is None 

743 else force_interactive 

744 ) 

745 

746 self._record_buffer_lock = threading.RLock() 

747 self._thread_locals = ConsoleThreadLocals( 

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

749 ) 

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

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

752 self._live: Optional["Live"] = None 

753 self._is_alt_screen = False 

754 

755 def __repr__(self) -> str: 

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

757 

758 @property 

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

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

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

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

763 if file is None: 

764 file = NULL_FILE 

765 return file 

766 

767 @file.setter 

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

769 """Set a new file object.""" 

770 self._file = new_file 

771 

772 @property 

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

774 """Get a thread local buffer.""" 

775 return self._thread_locals.buffer 

776 

777 @property 

778 def _buffer_index(self) -> int: 

779 """Get a thread local buffer.""" 

780 return self._thread_locals.buffer_index 

781 

782 @_buffer_index.setter 

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

784 self._thread_locals.buffer_index = value 

785 

786 @property 

787 def _theme_stack(self) -> ThemeStack: 

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

789 return self._thread_locals.theme_stack 

790 

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

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

793 if self.is_jupyter: 

794 return ColorSystem.TRUECOLOR 

795 if not self.is_terminal or self.is_dumb_terminal: 

796 return None 

797 if WINDOWS: # pragma: no cover 

798 if self.legacy_windows: # pragma: no cover 

799 return ColorSystem.WINDOWS 

800 windows_console_features = get_windows_console_features() 

801 return ( 

802 ColorSystem.TRUECOLOR 

803 if windows_console_features.truecolor 

804 else ColorSystem.EIGHT_BIT 

805 ) 

806 else: 

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

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

809 return ColorSystem.TRUECOLOR 

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

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

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

813 return color_system 

814 

815 def _enter_buffer(self) -> None: 

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

817 self._buffer_index += 1 

818 

819 def _exit_buffer(self) -> None: 

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

821 self._buffer_index -= 1 

822 self._check_buffer() 

823 

824 def set_live(self, live: "Live") -> None: 

825 """Set Live instance. Used by Live context manager. 

826 

827 Args: 

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

829 

830 Raises: 

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

832 """ 

833 with self._lock: 

834 if self._live is not None: 

835 raise errors.LiveError("Only one live display may be active at once") 

836 self._live = live 

837 

838 def clear_live(self) -> None: 

839 """Clear the Live instance.""" 

840 with self._lock: 

841 self._live = None 

842 

843 def push_render_hook(self, hook: RenderHook) -> None: 

844 """Add a new render hook to the stack. 

845 

846 Args: 

847 hook (RenderHook): Render hook instance. 

848 """ 

849 with self._lock: 

850 self._render_hooks.append(hook) 

851 

852 def pop_render_hook(self) -> None: 

853 """Pop the last renderhook from the stack.""" 

854 with self._lock: 

855 self._render_hooks.pop() 

856 

857 def __enter__(self) -> "Console": 

858 """Own context manager to enter buffer context.""" 

859 self._enter_buffer() 

860 return self 

861 

862 def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: 

863 """Exit buffer context.""" 

864 self._exit_buffer() 

865 

866 def begin_capture(self) -> None: 

867 """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output.""" 

868 self._enter_buffer() 

869 

870 def end_capture(self) -> str: 

871 """End capture mode and return captured string. 

872 

873 Returns: 

874 str: Console output. 

875 """ 

876 render_result = self._render_buffer(self._buffer) 

877 del self._buffer[:] 

878 self._exit_buffer() 

879 return render_result 

880 

881 def push_theme(self, theme: Theme, *, inherit: bool = True) -> None: 

882 """Push a new theme on to the top of the stack, replacing the styles from the previous theme. 

883 Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather 

884 than calling this method directly. 

885 

886 Args: 

887 theme (Theme): A theme instance. 

888 inherit (bool, optional): Inherit existing styles. Defaults to True. 

889 """ 

890 self._theme_stack.push_theme(theme, inherit=inherit) 

891 

892 def pop_theme(self) -> None: 

893 """Remove theme from top of stack, restoring previous theme.""" 

894 self._theme_stack.pop_theme() 

895 

896 def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext: 

897 """Use a different theme for the duration of the context manager. 

898 

899 Args: 

900 theme (Theme): Theme instance to user. 

901 inherit (bool, optional): Inherit existing console styles. Defaults to True. 

902 

903 Returns: 

904 ThemeContext: [description] 

905 """ 

906 return ThemeContext(self, theme, inherit) 

907 

908 @property 

909 def color_system(self) -> Optional[str]: 

910 """Get color system string. 

911 

912 Returns: 

913 Optional[str]: "standard", "256" or "truecolor". 

914 """ 

915 

916 if self._color_system is not None: 

917 return _COLOR_SYSTEMS_NAMES[self._color_system] 

918 else: 

919 return None 

920 

921 @property 

922 def encoding(self) -> str: 

923 """Get the encoding of the console file, e.g. ``"utf-8"``. 

924 

925 Returns: 

926 str: A standard encoding string. 

927 """ 

928 return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower() 

929 

930 @property 

931 def is_terminal(self) -> bool: 

932 """Check if the console is writing to a terminal. 

933 

934 Returns: 

935 bool: True if the console writing to a device capable of 

936 understanding terminal codes, otherwise False. 

937 """ 

938 if self._force_terminal is not None: 

939 return self._force_terminal 

940 

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

942 "idlelib" 

943 ): 

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

945 return False 

946 

947 if self.is_jupyter: 

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

949 return False 

950 

951 # If FORCE_COLOR env var has any value at all, we assume a terminal. 

952 force_color = self._environ.get("FORCE_COLOR") 

953 if force_color is not None: 

954 self._force_terminal = True 

955 

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

957 try: 

958 return False if isatty is None else isatty() 

959 except ValueError: 

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

961 # ValueError: I/O operation on closed file 

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

963 return False 

964 

965 @property 

966 def is_dumb_terminal(self) -> bool: 

967 """Detect dumb terminal. 

968 

969 Returns: 

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

971 

972 """ 

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

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

975 return self.is_terminal and is_dumb 

976 

977 @property 

978 def options(self) -> ConsoleOptions: 

979 """Get default console options.""" 

980 return ConsoleOptions( 

981 max_height=self.size.height, 

982 size=self.size, 

983 legacy_windows=self.legacy_windows, 

984 min_width=1, 

985 max_width=self.width, 

986 encoding=self.encoding, 

987 is_terminal=self.is_terminal, 

988 ) 

989 

990 @property 

991 def size(self) -> ConsoleDimensions: 

992 """Get the size of the console. 

993 

994 Returns: 

995 ConsoleDimensions: A named tuple containing the dimensions. 

996 """ 

997 

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

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

1000 

1001 if self.is_dumb_terminal: 

1002 return ConsoleDimensions(80, 25) 

1003 

1004 width: Optional[int] = None 

1005 height: Optional[int] = None 

1006 

1007 if WINDOWS: # pragma: no cover 

1008 try: 

1009 width, height = os.get_terminal_size() 

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

1011 pass 

1012 else: 

1013 for file_descriptor in _STD_STREAMS: 

1014 try: 

1015 width, height = os.get_terminal_size(file_descriptor) 

1016 except (AttributeError, ValueError, OSError): 

1017 pass 

1018 else: 

1019 break 

1020 

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

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

1023 width = int(columns) 

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

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

1026 height = int(lines) 

1027 

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

1029 width = width or 80 

1030 height = height or 25 

1031 return ConsoleDimensions( 

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

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

1034 ) 

1035 

1036 @size.setter 

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

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

1039 

1040 Args: 

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

1042 """ 

1043 width, height = new_size 

1044 self._width = width 

1045 self._height = height 

1046 

1047 @property 

1048 def width(self) -> int: 

1049 """Get the width of the console. 

1050 

1051 Returns: 

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

1053 """ 

1054 return self.size.width 

1055 

1056 @width.setter 

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

1058 """Set width. 

1059 

1060 Args: 

1061 width (int): New width. 

1062 """ 

1063 self._width = width 

1064 

1065 @property 

1066 def height(self) -> int: 

1067 """Get the height of the console. 

1068 

1069 Returns: 

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

1071 """ 

1072 return self.size.height 

1073 

1074 @height.setter 

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

1076 """Set height. 

1077 

1078 Args: 

1079 height (int): new height. 

1080 """ 

1081 self._height = height 

1082 

1083 def bell(self) -> None: 

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

1085 self.control(Control.bell()) 

1086 

1087 def capture(self) -> Capture: 

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

1089 rather than writing it to the console. 

1090 

1091 Example: 

1092 >>> from rich.console import Console 

1093 >>> console = Console() 

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

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

1096 >>> print(capture.get()) 

1097 

1098 Returns: 

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

1100 """ 

1101 capture = Capture(self) 

1102 return capture 

1103 

1104 def pager( 

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

1106 ) -> PagerContext: 

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

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

1109 

1110 Args: 

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

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

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

1114 

1115 Example: 

1116 >>> from rich.console import Console 

1117 >>> from rich.__main__ import make_test_card 

1118 >>> console = Console() 

1119 >>> with console.pager(): 

1120 console.print(make_test_card()) 

1121 

1122 Returns: 

1123 PagerContext: A context manager. 

1124 """ 

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

1126 

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

1128 """Write new line(s). 

1129 

1130 Args: 

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

1132 """ 

1133 

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

1135 self.print(NewLine(count)) 

1136 

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

1138 """Clear the screen. 

1139 

1140 Args: 

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

1142 """ 

1143 if home: 

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

1145 else: 

1146 self.control(Control.clear()) 

1147 

1148 def status( 

1149 self, 

1150 status: RenderableType, 

1151 *, 

1152 spinner: str = "dots", 

1153 spinner_style: StyleType = "status.spinner", 

1154 speed: float = 1.0, 

1155 refresh_per_second: float = 12.5, 

1156 ) -> "Status": 

1157 """Display a status and spinner. 

1158 

1159 Args: 

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

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

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

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

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

1165 

1166 Returns: 

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

1168 """ 

1169 from .status import Status 

1170 

1171 status_renderable = Status( 

1172 status, 

1173 console=self, 

1174 spinner=spinner, 

1175 spinner_style=spinner_style, 

1176 speed=speed, 

1177 refresh_per_second=refresh_per_second, 

1178 ) 

1179 return status_renderable 

1180 

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

1182 """Show or hide the cursor. 

1183 

1184 Args: 

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

1186 """ 

1187 if self.is_terminal: 

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

1189 return True 

1190 return False 

1191 

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

1193 """Enables alternative screen mode. 

1194 

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

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

1197 that handles this for you. 

1198 

1199 Args: 

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

1201 

1202 Returns: 

1203 bool: True if the control codes were written. 

1204 

1205 """ 

1206 changed = False 

1207 if self.is_terminal and not self.legacy_windows: 

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

1209 changed = True 

1210 self._is_alt_screen = enable 

1211 return changed 

1212 

1213 @property 

1214 def is_alt_screen(self) -> bool: 

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

1216 

1217 Returns: 

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

1219 """ 

1220 return self._is_alt_screen 

1221 

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

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

1224 

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

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

1227 exits. 

1228 

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

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

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

1232 

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

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

1235 

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

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

1238 using this method being overwritten. 

1239 

1240 Args: 

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

1242 

1243 Returns: 

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

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

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

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

1248 """ 

1249 if self.is_terminal: 

1250 self.control(Control.title(title)) 

1251 return True 

1252 return False 

1253 

1254 def screen( 

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

1256 ) -> "ScreenContext": 

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

1258 

1259 Args: 

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

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

1262 

1263 Returns: 

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

1265 """ 

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

1267 

1268 def measure( 

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

1270 ) -> Measurement: 

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

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

1273 

1274 Args: 

1275 renderable (RenderableType): Any renderable or string. 

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

1277 to use default options. Defaults to None. 

1278 

1279 Returns: 

1280 Measurement: A measurement of the renderable. 

1281 """ 

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

1283 return measurement 

1284 

1285 def render( 

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

1287 ) -> Iterable[Segment]: 

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

1289 

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

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

1292 

1293 Args: 

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

1295 an object that may be converted to a string. 

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

1297 

1298 Returns: 

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

1300 """ 

1301 

1302 _options = options or self.options 

1303 if _options.max_width < 1: 

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

1305 return 

1306 render_iterable: RenderResult 

1307 

1308 renderable = rich_cast(renderable) 

1309 if hasattr(renderable, "__rich_console__") and not isclass(renderable): 

1310 render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr] 

1311 elif isinstance(renderable, str): 

1312 text_renderable = self.render_str( 

1313 renderable, highlight=_options.highlight, markup=_options.markup 

1314 ) 

1315 render_iterable = text_renderable.__rich_console__(self, _options) 

1316 else: 

1317 raise errors.NotRenderableError( 

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

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

1320 ) 

1321 

1322 try: 

1323 iter_render = iter(render_iterable) 

1324 except TypeError: 

1325 raise errors.NotRenderableError( 

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

1327 ) 

1328 _Segment = Segment 

1329 _options = _options.reset_height() 

1330 for render_output in iter_render: 

1331 if isinstance(render_output, _Segment): 

1332 yield render_output 

1333 else: 

1334 yield from self.render(render_output, _options) 

1335 

1336 def render_lines( 

1337 self, 

1338 renderable: RenderableType, 

1339 options: Optional[ConsoleOptions] = None, 

1340 *, 

1341 style: Optional[Style] = None, 

1342 pad: bool = True, 

1343 new_lines: bool = False, 

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

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

1346 

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

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

1349 

1350 Args: 

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

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

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

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

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

1356 

1357 Returns: 

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

1359 """ 

1360 with self._lock: 

1361 render_options = options or self.options 

1362 _rendered = self.render(renderable, render_options) 

1363 if style: 

1364 _rendered = Segment.apply_style(_rendered, style) 

1365 

1366 render_height = render_options.height 

1367 if render_height is not None: 

1368 render_height = max(0, render_height) 

1369 

1370 lines = list( 

1371 islice( 

1372 Segment.split_and_crop_lines( 

1373 _rendered, 

1374 render_options.max_width, 

1375 include_new_lines=new_lines, 

1376 pad=pad, 

1377 style=style, 

1378 ), 

1379 None, 

1380 render_height, 

1381 ) 

1382 ) 

1383 if render_options.height is not None: 

1384 extra_lines = render_options.height - len(lines) 

1385 if extra_lines > 0: 

1386 pad_line = [ 

1387 [Segment(" " * render_options.max_width, style), Segment("\n")] 

1388 if new_lines 

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

1390 ] 

1391 lines.extend(pad_line * extra_lines) 

1392 

1393 return lines 

1394 

1395 def render_str( 

1396 self, 

1397 text: str, 

1398 *, 

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

1400 justify: Optional[JustifyMethod] = None, 

1401 overflow: Optional[OverflowMethod] = None, 

1402 emoji: Optional[bool] = None, 

1403 markup: Optional[bool] = None, 

1404 highlight: Optional[bool] = None, 

1405 highlighter: Optional[HighlighterType] = None, 

1406 ) -> "Text": 

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

1408 you print or log a string. 

1409 

1410 Args: 

1411 text (str): Text to render. 

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

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

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

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

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

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

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

1419 Returns: 

1420 ConsoleRenderable: Renderable object. 

1421 

1422 """ 

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

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

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

1426 

1427 if markup_enabled: 

1428 rich_text = render_markup( 

1429 text, 

1430 style=style, 

1431 emoji=emoji_enabled, 

1432 emoji_variant=self._emoji_variant, 

1433 ) 

1434 rich_text.justify = justify 

1435 rich_text.overflow = overflow 

1436 else: 

1437 rich_text = Text( 

1438 _emoji_replace(text, default_variant=self._emoji_variant) 

1439 if emoji_enabled 

1440 else text, 

1441 justify=justify, 

1442 overflow=overflow, 

1443 style=style, 

1444 ) 

1445 

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

1447 if _highlighter is not None: 

1448 highlight_text = _highlighter(str(rich_text)) 

1449 highlight_text.copy_styles(rich_text) 

1450 return highlight_text 

1451 

1452 return rich_text 

1453 

1454 def get_style( 

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

1456 ) -> Style: 

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

1458 

1459 Args: 

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

1461 

1462 Returns: 

1463 Style: A Style object. 

1464 

1465 Raises: 

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

1467 

1468 """ 

1469 if isinstance(name, Style): 

1470 return name 

1471 

1472 try: 

1473 style = self._theme_stack.get(name) 

1474 if style is None: 

1475 style = Style.parse(name) 

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

1477 except errors.StyleSyntaxError as error: 

1478 if default is not None: 

1479 return self.get_style(default) 

1480 raise errors.MissingStyle( 

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

1482 ) from None 

1483 

1484 def _collect_renderables( 

1485 self, 

1486 objects: Iterable[Any], 

1487 sep: str, 

1488 end: str, 

1489 *, 

1490 justify: Optional[JustifyMethod] = None, 

1491 emoji: Optional[bool] = None, 

1492 markup: Optional[bool] = None, 

1493 highlight: Optional[bool] = None, 

1494 ) -> List[ConsoleRenderable]: 

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

1496 

1497 Args: 

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

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

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

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

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

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

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

1505 

1506 Returns: 

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

1508 """ 

1509 renderables: List[ConsoleRenderable] = [] 

1510 _append = renderables.append 

1511 text: List[Text] = [] 

1512 append_text = text.append 

1513 

1514 append = _append 

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

1516 

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

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

1519 

1520 append = align_append 

1521 

1522 _highlighter: HighlighterType = _null_highlighter 

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

1524 _highlighter = self.highlighter 

1525 

1526 def check_text() -> None: 

1527 if text: 

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

1529 append(sep_text.join(text)) 

1530 text.clear() 

1531 

1532 for renderable in objects: 

1533 renderable = rich_cast(renderable) 

1534 if isinstance(renderable, str): 

1535 append_text( 

1536 self.render_str( 

1537 renderable, emoji=emoji, markup=markup, highlighter=_highlighter 

1538 ) 

1539 ) 

1540 elif isinstance(renderable, Text): 

1541 append_text(renderable) 

1542 elif isinstance(renderable, ConsoleRenderable): 

1543 check_text() 

1544 append(renderable) 

1545 elif is_expandable(renderable): 

1546 check_text() 

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

1548 else: 

1549 append_text(_highlighter(str(renderable))) 

1550 

1551 check_text() 

1552 

1553 if self.style is not None: 

1554 style = self.get_style(self.style) 

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

1556 

1557 return renderables 

1558 

1559 def rule( 

1560 self, 

1561 title: TextType = "", 

1562 *, 

1563 characters: str = "─", 

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

1565 align: AlignMethod = "center", 

1566 ) -> None: 

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

1568 

1569 Args: 

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

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

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

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

1574 """ 

1575 from .rule import Rule 

1576 

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

1578 self.print(rule) 

1579 

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

1581 """Insert non-printing control codes. 

1582 

1583 Args: 

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

1585 """ 

1586 if not self.is_dumb_terminal: 

1587 with self: 

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

1589 

1590 def out( 

1591 self, 

1592 *objects: Any, 

1593 sep: str = " ", 

1594 end: str = "\n", 

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

1596 highlight: Optional[bool] = None, 

1597 ) -> None: 

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

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

1600 optionally apply highlighting and a basic style. 

1601 

1602 Args: 

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

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

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

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

1607 console default. Defaults to ``None``. 

1608 """ 

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

1610 self.print( 

1611 raw_output, 

1612 style=style, 

1613 highlight=highlight, 

1614 emoji=False, 

1615 markup=False, 

1616 no_wrap=True, 

1617 overflow="ignore", 

1618 crop=False, 

1619 end=end, 

1620 ) 

1621 

1622 def print( 

1623 self, 

1624 *objects: Any, 

1625 sep: str = " ", 

1626 end: str = "\n", 

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

1628 justify: Optional[JustifyMethod] = None, 

1629 overflow: Optional[OverflowMethod] = None, 

1630 no_wrap: Optional[bool] = None, 

1631 emoji: Optional[bool] = None, 

1632 markup: Optional[bool] = None, 

1633 highlight: Optional[bool] = None, 

1634 width: Optional[int] = None, 

1635 height: Optional[int] = None, 

1636 crop: bool = True, 

1637 soft_wrap: Optional[bool] = None, 

1638 new_line_start: bool = False, 

1639 ) -> None: 

1640 """Print to the console. 

1641 

1642 Args: 

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

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

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

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

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

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

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

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

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

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

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

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

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

1656 Console default. Defaults to ``None``. 

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

1658 """ 

1659 if not objects: 

1660 objects = (NewLine(),) 

1661 

1662 if soft_wrap is None: 

1663 soft_wrap = self.soft_wrap 

1664 if soft_wrap: 

1665 if no_wrap is None: 

1666 no_wrap = True 

1667 if overflow is None: 

1668 overflow = "ignore" 

1669 crop = False 

1670 render_hooks = self._render_hooks[:] 

1671 with self: 

1672 renderables = self._collect_renderables( 

1673 objects, 

1674 sep, 

1675 end, 

1676 justify=justify, 

1677 emoji=emoji, 

1678 markup=markup, 

1679 highlight=highlight, 

1680 ) 

1681 for hook in render_hooks: 

1682 renderables = hook.process_renderables(renderables) 

1683 render_options = self.options.update( 

1684 justify=justify, 

1685 overflow=overflow, 

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

1687 height=height, 

1688 no_wrap=no_wrap, 

1689 markup=markup, 

1690 highlight=highlight, 

1691 ) 

1692 

1693 new_segments: List[Segment] = [] 

1694 extend = new_segments.extend 

1695 render = self.render 

1696 if style is None: 

1697 for renderable in renderables: 

1698 extend(render(renderable, render_options)) 

1699 else: 

1700 for renderable in renderables: 

1701 extend( 

1702 Segment.apply_style( 

1703 render(renderable, render_options), self.get_style(style) 

1704 ) 

1705 ) 

1706 if new_line_start: 

1707 if ( 

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

1709 > 1 

1710 ): 

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

1712 if crop: 

1713 buffer_extend = self._buffer.extend 

1714 for line in Segment.split_and_crop_lines( 

1715 new_segments, self.width, pad=False 

1716 ): 

1717 buffer_extend(line) 

1718 else: 

1719 self._buffer.extend(new_segments) 

1720 

1721 def print_json( 

1722 self, 

1723 json: Optional[str] = None, 

1724 *, 

1725 data: Any = None, 

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

1727 highlight: bool = True, 

1728 skip_keys: bool = False, 

1729 ensure_ascii: bool = False, 

1730 check_circular: bool = True, 

1731 allow_nan: bool = True, 

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

1733 sort_keys: bool = False, 

1734 ) -> None: 

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

1736 

1737 Args: 

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

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

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

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

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

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

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

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

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

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

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

1749 """ 

1750 from pip._vendor.rich.json import JSON 

1751 

1752 if json is None: 

1753 json_renderable = JSON.from_data( 

1754 data, 

1755 indent=indent, 

1756 highlight=highlight, 

1757 skip_keys=skip_keys, 

1758 ensure_ascii=ensure_ascii, 

1759 check_circular=check_circular, 

1760 allow_nan=allow_nan, 

1761 default=default, 

1762 sort_keys=sort_keys, 

1763 ) 

1764 else: 

1765 if not isinstance(json, str): 

1766 raise TypeError( 

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

1768 ) 

1769 json_renderable = JSON( 

1770 json, 

1771 indent=indent, 

1772 highlight=highlight, 

1773 skip_keys=skip_keys, 

1774 ensure_ascii=ensure_ascii, 

1775 check_circular=check_circular, 

1776 allow_nan=allow_nan, 

1777 default=default, 

1778 sort_keys=sort_keys, 

1779 ) 

1780 self.print(json_renderable, soft_wrap=True) 

1781 

1782 def update_screen( 

1783 self, 

1784 renderable: RenderableType, 

1785 *, 

1786 region: Optional[Region] = None, 

1787 options: Optional[ConsoleOptions] = None, 

1788 ) -> None: 

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

1790 

1791 Args: 

1792 renderable (RenderableType): A Rich renderable. 

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

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

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

1796 

1797 Raises: 

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

1799 

1800 """ 

1801 if not self.is_alt_screen: 

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

1803 render_options = options or self.options 

1804 if region is None: 

1805 x = y = 0 

1806 render_options = render_options.update_dimensions( 

1807 render_options.max_width, render_options.height or self.height 

1808 ) 

1809 else: 

1810 x, y, width, height = region 

1811 render_options = render_options.update_dimensions(width, height) 

1812 

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

1814 self.update_screen_lines(lines, x, y) 

1815 

1816 def update_screen_lines( 

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

1818 ) -> None: 

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

1820 

1821 Args: 

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

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

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

1825 

1826 Raises: 

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

1828 """ 

1829 if not self.is_alt_screen: 

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

1831 screen_update = ScreenUpdate(lines, x, y) 

1832 segments = self.render(screen_update) 

1833 self._buffer.extend(segments) 

1834 self._check_buffer() 

1835 

1836 def print_exception( 

1837 self, 

1838 *, 

1839 width: Optional[int] = 100, 

1840 extra_lines: int = 3, 

1841 theme: Optional[str] = None, 

1842 word_wrap: bool = False, 

1843 show_locals: bool = False, 

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

1845 max_frames: int = 100, 

1846 ) -> None: 

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

1848 

1849 Args: 

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

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

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

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

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

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

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

1857 """ 

1858 from .traceback import Traceback 

1859 

1860 traceback = Traceback( 

1861 width=width, 

1862 extra_lines=extra_lines, 

1863 theme=theme, 

1864 word_wrap=word_wrap, 

1865 show_locals=show_locals, 

1866 suppress=suppress, 

1867 max_frames=max_frames, 

1868 ) 

1869 self.print(traceback) 

1870 

1871 @staticmethod 

1872 def _caller_frame_info( 

1873 offset: int, 

1874 currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe, 

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

1876 """Get caller frame information. 

1877 

1878 Args: 

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

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

1881 retrieve the current frame. Defaults to ``inspect.currentframe``. 

1882 

1883 Returns: 

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

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

1886 

1887 Raises: 

1888 RuntimeError: If the stack offset is invalid. 

1889 """ 

1890 # Ignore the frame of this local helper 

1891 offset += 1 

1892 

1893 frame = currentframe() 

1894 if frame is not None: 

1895 # Use the faster currentframe where implemented 

1896 while offset and frame is not None: 

1897 frame = frame.f_back 

1898 offset -= 1 

1899 assert frame is not None 

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

1901 else: 

1902 # Fallback to the slower stack 

1903 frame_info = inspect.stack()[offset] 

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

1905 

1906 def log( 

1907 self, 

1908 *objects: Any, 

1909 sep: str = " ", 

1910 end: str = "\n", 

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

1912 justify: Optional[JustifyMethod] = None, 

1913 emoji: Optional[bool] = None, 

1914 markup: Optional[bool] = None, 

1915 highlight: Optional[bool] = None, 

1916 log_locals: bool = False, 

1917 _stack_offset: int = 1, 

1918 ) -> None: 

1919 """Log rich content to the terminal. 

1920 

1921 Args: 

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

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

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

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

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

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

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

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

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

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

1932 was called. Defaults to False. 

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

1934 """ 

1935 if not objects: 

1936 objects = (NewLine(),) 

1937 

1938 render_hooks = self._render_hooks[:] 

1939 

1940 with self: 

1941 renderables = self._collect_renderables( 

1942 objects, 

1943 sep, 

1944 end, 

1945 justify=justify, 

1946 emoji=emoji, 

1947 markup=markup, 

1948 highlight=highlight, 

1949 ) 

1950 if style is not None: 

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

1952 

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

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

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

1956 if log_locals: 

1957 locals_map = { 

1958 key: value 

1959 for key, value in locals.items() 

1960 if not key.startswith("__") 

1961 } 

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

1963 

1964 renderables = [ 

1965 self._log_render( 

1966 self, 

1967 renderables, 

1968 log_time=self.get_datetime(), 

1969 path=path, 

1970 line_no=line_no, 

1971 link_path=link_path, 

1972 ) 

1973 ] 

1974 for hook in render_hooks: 

1975 renderables = hook.process_renderables(renderables) 

1976 new_segments: List[Segment] = [] 

1977 extend = new_segments.extend 

1978 render = self.render 

1979 render_options = self.options 

1980 for renderable in renderables: 

1981 extend(render(renderable, render_options)) 

1982 buffer_extend = self._buffer.extend 

1983 for line in Segment.split_and_crop_lines( 

1984 new_segments, self.width, pad=False 

1985 ): 

1986 buffer_extend(line) 

1987 

1988 def _check_buffer(self) -> None: 

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

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

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

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

1993 """ 

1994 if self.quiet: 

1995 del self._buffer[:] 

1996 return 

1997 with self._lock: 

1998 if self.record: 

1999 with self._record_buffer_lock: 

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

2001 

2002 if self._buffer_index == 0: 

2003 

2004 if self.is_jupyter: # pragma: no cover 

2005 from .jupyter import display 

2006 

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

2008 del self._buffer[:] 

2009 else: 

2010 if WINDOWS: 

2011 use_legacy_windows_render = False 

2012 if self.legacy_windows: 

2013 fileno = get_fileno(self.file) 

2014 if fileno is not None: 

2015 use_legacy_windows_render = ( 

2016 fileno in _STD_STREAMS_OUTPUT 

2017 ) 

2018 

2019 if use_legacy_windows_render: 

2020 from pip._vendor.rich._win32_console import LegacyWindowsTerm 

2021 from pip._vendor.rich._windows_renderer import legacy_windows_render 

2022 

2023 buffer = self._buffer[:] 

2024 if self.no_color and self._color_system: 

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

2026 

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

2028 else: 

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

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

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

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

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

2034 write = self.file.write 

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

2036 MAX_WRITE = 32 * 1024 // 4 

2037 try: 

2038 if len(text) <= MAX_WRITE: 

2039 write(text) 

2040 else: 

2041 batch: List[str] = [] 

2042 batch_append = batch.append 

2043 size = 0 

2044 for line in text.splitlines(True): 

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

2046 write("".join(batch)) 

2047 batch.clear() 

2048 size = 0 

2049 batch_append(line) 

2050 size += len(line) 

2051 if batch: 

2052 write("".join(batch)) 

2053 batch.clear() 

2054 except UnicodeEncodeError as error: 

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

2056 raise 

2057 else: 

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

2059 try: 

2060 self.file.write(text) 

2061 except UnicodeEncodeError as error: 

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

2063 raise 

2064 

2065 self.file.flush() 

2066 del self._buffer[:] 

2067 

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

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

2070 output: List[str] = [] 

2071 append = output.append 

2072 color_system = self._color_system 

2073 legacy_windows = self.legacy_windows 

2074 not_terminal = not self.is_terminal 

2075 if self.no_color and color_system: 

2076 buffer = Segment.remove_color(buffer) 

2077 for text, style, control in buffer: 

2078 if style: 

2079 append( 

2080 style.render( 

2081 text, 

2082 color_system=color_system, 

2083 legacy_windows=legacy_windows, 

2084 ) 

2085 ) 

2086 elif not (not_terminal and control): 

2087 append(text) 

2088 

2089 rendered = "".join(output) 

2090 return rendered 

2091 

2092 def input( 

2093 self, 

2094 prompt: TextType = "", 

2095 *, 

2096 markup: bool = True, 

2097 emoji: bool = True, 

2098 password: bool = False, 

2099 stream: Optional[TextIO] = None, 

2100 ) -> str: 

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

2102 

2103 It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded. 

2104 

2105 Args: 

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

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

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

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

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

2111 

2112 Returns: 

2113 str: Text read from stdin. 

2114 """ 

2115 if prompt: 

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

2117 if password: 

2118 result = getpass("", stream=stream) 

2119 else: 

2120 if stream: 

2121 result = stream.readline() 

2122 else: 

2123 result = input() 

2124 return result 

2125 

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

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

2128 

2129 Args: 

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

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

2132 Defaults to ``False``. 

2133 

2134 Returns: 

2135 str: String containing console contents. 

2136 

2137 """ 

2138 assert ( 

2139 self.record 

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

2141 

2142 with self._record_buffer_lock: 

2143 if styles: 

2144 text = "".join( 

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

2146 for text, style, _ in self._record_buffer 

2147 ) 

2148 else: 

2149 text = "".join( 

2150 segment.text 

2151 for segment in self._record_buffer 

2152 if not segment.control 

2153 ) 

2154 if clear: 

2155 del self._record_buffer[:] 

2156 return text 

2157 

2158 def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None: 

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

2160 

2161 Args: 

2162 path (str): Path to write text files. 

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

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

2165 Defaults to ``False``. 

2166 

2167 """ 

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

2169 with open(path, "wt", encoding="utf-8") as write_file: 

2170 write_file.write(text) 

2171 

2172 def export_html( 

2173 self, 

2174 *, 

2175 theme: Optional[TerminalTheme] = None, 

2176 clear: bool = True, 

2177 code_format: Optional[str] = None, 

2178 inline_styles: bool = False, 

2179 ) -> str: 

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

2181 

2182 Args: 

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

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

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

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

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

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

2189 Defaults to False. 

2190 

2191 Returns: 

2192 str: String containing console contents as HTML. 

2193 """ 

2194 assert ( 

2195 self.record 

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

2197 fragments: List[str] = [] 

2198 append = fragments.append 

2199 _theme = theme or DEFAULT_TERMINAL_THEME 

2200 stylesheet = "" 

2201 

2202 render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format 

2203 

2204 with self._record_buffer_lock: 

2205 if inline_styles: 

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

2207 Segment.simplify(self._record_buffer) 

2208 ): 

2209 text = escape(text) 

2210 if style: 

2211 rule = style.get_html_style(_theme) 

2212 if style.link: 

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

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

2215 append(text) 

2216 else: 

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

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

2219 Segment.simplify(self._record_buffer) 

2220 ): 

2221 text = escape(text) 

2222 if style: 

2223 rule = style.get_html_style(_theme) 

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

2225 if style.link: 

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

2227 else: 

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

2229 append(text) 

2230 stylesheet_rules: List[str] = [] 

2231 stylesheet_append = stylesheet_rules.append 

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

2233 if style_rule: 

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

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

2236 

2237 rendered_code = render_code_format.format( 

2238 code="".join(fragments), 

2239 stylesheet=stylesheet, 

2240 foreground=_theme.foreground_color.hex, 

2241 background=_theme.background_color.hex, 

2242 ) 

2243 if clear: 

2244 del self._record_buffer[:] 

2245 return rendered_code 

2246 

2247 def save_html( 

2248 self, 

2249 path: str, 

2250 *, 

2251 theme: Optional[TerminalTheme] = None, 

2252 clear: bool = True, 

2253 code_format: str = CONSOLE_HTML_FORMAT, 

2254 inline_styles: bool = False, 

2255 ) -> None: 

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

2257 

2258 Args: 

2259 path (str): Path to write html file. 

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

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

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

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

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

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

2266 Defaults to False. 

2267 

2268 """ 

2269 html = self.export_html( 

2270 theme=theme, 

2271 clear=clear, 

2272 code_format=code_format, 

2273 inline_styles=inline_styles, 

2274 ) 

2275 with open(path, "wt", encoding="utf-8") as write_file: 

2276 write_file.write(html) 

2277 

2278 def export_svg( 

2279 self, 

2280 *, 

2281 title: str = "Rich", 

2282 theme: Optional[TerminalTheme] = None, 

2283 clear: bool = True, 

2284 code_format: str = CONSOLE_SVG_FORMAT, 

2285 font_aspect_ratio: float = 0.61, 

2286 unique_id: Optional[str] = None, 

2287 ) -> str: 

2288 """ 

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

2290 

2291 Args: 

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

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

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

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

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

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

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

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

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

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

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

2303 """ 

2304 

2305 from pip._vendor.rich.cells import cell_len 

2306 

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

2308 

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

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

2311 if style in style_cache: 

2312 return style_cache[style] 

2313 css_rules = [] 

2314 color = ( 

2315 _theme.foreground_color 

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

2317 else style.color.get_truecolor(_theme) 

2318 ) 

2319 bgcolor = ( 

2320 _theme.background_color 

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

2322 else style.bgcolor.get_truecolor(_theme) 

2323 ) 

2324 if style.reverse: 

2325 color, bgcolor = bgcolor, color 

2326 if style.dim: 

2327 color = blend_rgb(color, bgcolor, 0.4) 

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

2329 if style.bold: 

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

2331 if style.italic: 

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

2333 if style.underline: 

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

2335 if style.strike: 

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

2337 

2338 css = ";".join(css_rules) 

2339 style_cache[style] = css 

2340 return css 

2341 

2342 _theme = theme or SVG_EXPORT_THEME 

2343 

2344 width = self.width 

2345 char_height = 20 

2346 char_width = char_height * font_aspect_ratio 

2347 line_height = char_height * 1.22 

2348 

2349 margin_top = 1 

2350 margin_right = 1 

2351 margin_bottom = 1 

2352 margin_left = 1 

2353 

2354 padding_top = 40 

2355 padding_right = 8 

2356 padding_bottom = 8 

2357 padding_left = 8 

2358 

2359 padding_width = padding_left + padding_right 

2360 padding_height = padding_top + padding_bottom 

2361 margin_width = margin_left + margin_right 

2362 margin_height = margin_top + margin_bottom 

2363 

2364 text_backgrounds: List[str] = [] 

2365 text_group: List[str] = [] 

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

2367 style_no = 1 

2368 

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

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

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

2372 

2373 def make_tag( 

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

2375 ) -> str: 

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

2377 

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

2379 if isinstance(value, (float)): 

2380 return format(value, "g") 

2381 return str(value) 

2382 

2383 tag_attribs = " ".join( 

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

2385 for k, v in attribs.items() 

2386 ) 

2387 return ( 

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

2389 if content 

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

2391 ) 

2392 

2393 with self._record_buffer_lock: 

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

2395 if clear: 

2396 self._record_buffer.clear() 

2397 

2398 if unique_id is None: 

2399 unique_id = "terminal-" + str( 

2400 zlib.adler32( 

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

2402 "utf-8", 

2403 "ignore", 

2404 ) 

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

2406 ) 

2407 ) 

2408 y = 0 

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

2410 x = 0 

2411 for text, style, _control in line: 

2412 style = style or Style() 

2413 rules = get_svg_style(style) 

2414 if rules not in classes: 

2415 classes[rules] = style_no 

2416 style_no += 1 

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

2418 

2419 if style.reverse: 

2420 has_background = True 

2421 background = ( 

2422 _theme.foreground_color.hex 

2423 if style.color is None 

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

2425 ) 

2426 else: 

2427 bgcolor = style.bgcolor 

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

2429 background = ( 

2430 _theme.background_color.hex 

2431 if style.bgcolor is None 

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

2433 ) 

2434 

2435 text_length = cell_len(text) 

2436 if has_background: 

2437 text_backgrounds.append( 

2438 make_tag( 

2439 "rect", 

2440 fill=background, 

2441 x=x * char_width, 

2442 y=y * line_height + 1.5, 

2443 width=char_width * text_length, 

2444 height=line_height + 0.25, 

2445 shape_rendering="crispEdges", 

2446 ) 

2447 ) 

2448 

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

2450 text_group.append( 

2451 make_tag( 

2452 "text", 

2453 escape_text(text), 

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

2455 x=x * char_width, 

2456 y=y * line_height + char_height, 

2457 textLength=char_width * len(text), 

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

2459 ) 

2460 ) 

2461 x += cell_len(text) 

2462 

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

2464 lines = "\n".join( 

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

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

2467 </clipPath>""" 

2468 for line_no, offset in enumerate(line_offsets) 

2469 ) 

2470 

2471 styles = "\n".join( 

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

2473 ) 

2474 backgrounds = "".join(text_backgrounds) 

2475 matrix = "".join(text_group) 

2476 

2477 terminal_width = ceil(width * char_width + padding_width) 

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

2479 chrome = make_tag( 

2480 "rect", 

2481 fill=_theme.background_color.hex, 

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

2483 stroke_width="1", 

2484 x=margin_left, 

2485 y=margin_top, 

2486 width=terminal_width, 

2487 height=terminal_height, 

2488 rx=8, 

2489 ) 

2490 

2491 title_color = _theme.foreground_color.hex 

2492 if title: 

2493 chrome += make_tag( 

2494 "text", 

2495 escape_text(title), 

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

2497 fill=title_color, 

2498 text_anchor="middle", 

2499 x=terminal_width // 2, 

2500 y=margin_top + char_height + 6, 

2501 ) 

2502 chrome += f""" 

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

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

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

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

2507 </g> 

2508 """ 

2509 

2510 svg = code_format.format( 

2511 unique_id=unique_id, 

2512 char_width=char_width, 

2513 char_height=char_height, 

2514 line_height=line_height, 

2515 terminal_width=char_width * width - 1, 

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

2517 width=terminal_width + margin_width, 

2518 height=terminal_height + margin_height, 

2519 terminal_x=margin_left + padding_left, 

2520 terminal_y=margin_top + padding_top, 

2521 styles=styles, 

2522 chrome=chrome, 

2523 backgrounds=backgrounds, 

2524 matrix=matrix, 

2525 lines=lines, 

2526 ) 

2527 return svg 

2528 

2529 def save_svg( 

2530 self, 

2531 path: str, 

2532 *, 

2533 title: str = "Rich", 

2534 theme: Optional[TerminalTheme] = None, 

2535 clear: bool = True, 

2536 code_format: str = CONSOLE_SVG_FORMAT, 

2537 font_aspect_ratio: float = 0.61, 

2538 unique_id: Optional[str] = None, 

2539 ) -> None: 

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

2541 

2542 Args: 

2543 path (str): The path to write the SVG to. 

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

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

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

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

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

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

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

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

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

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

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

2555 """ 

2556 svg = self.export_svg( 

2557 title=title, 

2558 theme=theme, 

2559 clear=clear, 

2560 code_format=code_format, 

2561 font_aspect_ratio=font_aspect_ratio, 

2562 unique_id=unique_id, 

2563 ) 

2564 with open(path, "wt", encoding="utf-8") as write_file: 

2565 write_file.write(svg) 

2566 

2567 

2568def _svg_hash(svg_main_code: str) -> str: 

2569 """Returns a unique hash for the given SVG main code. 

2570 

2571 Args: 

2572 svg_main_code (str): The content we're going to inject in the SVG envelope. 

2573 

2574 Returns: 

2575 str: a hash of the given content 

2576 """ 

2577 return str(zlib.adler32(svg_main_code.encode())) 

2578 

2579 

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

2581 console = Console(record=True) 

2582 

2583 console.log( 

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

2585 5, 

2586 1.3, 

2587 True, 

2588 False, 

2589 None, 

2590 { 

2591 "jsonrpc": "2.0", 

2592 "method": "subtract", 

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

2594 "id": 3, 

2595 }, 

2596 ) 

2597 

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

2599 

2600 console.print( 

2601 { 

2602 "name": None, 

2603 "empty": [], 

2604 "quiz": { 

2605 "sport": { 

2606 "answered": True, 

2607 "q1": { 

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

2609 "options": [ 

2610 "New York Bulls", 

2611 "Los Angeles Kings", 

2612 "Golden State Warriors", 

2613 "Huston Rocket", 

2614 ], 

2615 "answer": "Huston Rocket", 

2616 }, 

2617 }, 

2618 "maths": { 

2619 "answered": False, 

2620 "q1": { 

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

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

2623 "answer": 12, 

2624 }, 

2625 "q2": { 

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

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

2628 "answer": 4, 

2629 }, 

2630 }, 

2631 }, 

2632 } 

2633 )