Coverage for /run/media/veganeco/water/status600.com/pypi/reptilian_climates/modules_series_2/ships/fields/gardens_pip/rich/console.py: 46%

1 import inspect

2 import os

3 import platform

4 import sys

5 import threading

6 import zlib

7 from abc import ABC, abstractmethod

8 from dataclasses import dataclass, field

9 from datetime import datetime

10 from functools import wraps

11 from getpass import getpass

12 from html import escape

13 from inspect import isclass

14 from itertools import islice

15 from math import ceil

16 from time import monotonic

17 from types import FrameType, ModuleType, TracebackType

18 from typing import (

19 IO,

20 TYPE_CHECKING,

21 Any,

22 Callable,

23 Dict,

24 Iterable,

25 List,

26 Mapping,

27 NamedTuple,

28 Optional,

29 TextIO,

30 Tuple,

31 Type,

32 Union,

33 cast,

34)

35

36 from rich._null_file import NULL_FILE

37

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

39 from typing import Literal, Protocol, runtime_checkable

40 else:

41 from typing_extensions import (

42 Literal,

43 Protocol,

44 runtime_checkable,

45 ) # pragma: no cover

46

47 from . import errors, themes

48 from ._emoji_replace import _emoji_replace

49 from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT

50 from ._fileno import get_fileno

51 from ._log_render import FormatTimeCallable, LogRender

52 from .align import Align, AlignMethod

53 from .color import ColorSystem, blend_rgb

54 from .control import Control

55 from .emoji import EmojiVariant

56 from .highlighter import NullHighlighter, ReprHighlighter

57 from .markup import render as render_markup

58 from .measure import Measurement, measure_renderables

59 from .pager import Pager, SystemPager

60 from .pretty import Pretty, is_expandable

61 from .protocol import rich_cast

62 from .region import Region

63 from .scope import render_scope

64 from .screen import Screen

65 from .segment import Segment

66 from .style import Style, StyleType

67 from .styled import Styled

68 from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme

69 from .text import Text, TextType

70 from .theme import Theme, ThemeStack

71

72 if TYPE_CHECKING:

73 from ._windows import WindowsConsoleFeatures

74 from .live import Live

75 from .status import Status

76

77 JUPYTER_DEFAULT_COLUMNS = 115

78 JUPYTER_DEFAULT_LINES = 100

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

80

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

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

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

84

85

86 class NoChange:

87 pass

88

89

90 NO_CHANGE = NoChange()

91

92 try:

93 _STDIN_FILENO = sys.__stdin__.fileno()

94 except Exception:

95 _STDIN_FILENO = 0

96 try:

97 _STDOUT_FILENO = sys.__stdout__.fileno()

98 except Exception:

99 _STDOUT_FILENO = 1

100 try:

101 _STDERR_FILENO = sys.__stderr__.fileno()

102 except Exception:

103 _STDERR_FILENO = 2

104

105 _STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO)

106 _STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO)

107

108

109 _TERM_COLORS = {

110 "kitty": ColorSystem.EIGHT_BIT,

111 "256color": ColorSystem.EIGHT_BIT,

112 "16color": ColorSystem.STANDARD,

113}

114

115

116 class ConsoleDimensions(NamedTuple):

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

118

119 width: int

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

121 height: int

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

123

124

125@dataclass

126 class ConsoleOptions:

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

128

129 size: ConsoleDimensions

130 """Size of console."""

131 legacy_windows: bool

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

133 min_width: int

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

135 max_width: int

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

137 is_terminal: bool

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

139 encoding: str

140 """Encoding of terminal."""

141 max_height: int

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

143 justify: Optional[JustifyMethod] = None

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

145 overflow: Optional[OverflowMethod] = None

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

147 no_wrap: Optional[bool] = False

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

149 highlight: Optional[bool] = None

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

151 markup: Optional[bool] = None

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

153 height: Optional[int] = None

154

155 @property

156 def ascii_only(self) -> bool:

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

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

159

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

161 """Return a copy of the options.

162

163 Returns:

164 ConsoleOptions: a copy of self.

165 """

166 options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions)

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

168 return options

169

170 def update(

171 self,

172 *,

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

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

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

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

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

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

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

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

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

182 ) -> "ConsoleOptions":

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

184 options = self.copy()

185 if not isinstance(width, NoChange):

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

187 if not isinstance(min_width, NoChange):

188 options.min_width = min_width

189 if not isinstance(max_width, NoChange):

190 options.max_width = max_width

191 if not isinstance(justify, NoChange):

192 options.justify = justify

193 if not isinstance(overflow, NoChange):

194 options.overflow = overflow

195 if not isinstance(no_wrap, NoChange):

196 options.no_wrap = no_wrap

197 if not isinstance(highlight, NoChange):

198 options.highlight = highlight

199 if not isinstance(markup, NoChange):

200 options.markup = markup

201 if not isinstance(height, NoChange):

202 if height is not None:

203 options.max_height = height

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

205 return options

206

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

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

209

210 Args:

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

212

213 Returns:

214 ~ConsoleOptions: New console options instance.

215 """

216 options = self.copy()

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

218 return options

219

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

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

222

223 Args:

224 height (int): New height

225

226 Returns:

227 ~ConsoleOptions: New Console options instance.

228 """

229 options = self.copy()

230 options.max_height = options.height = height

231 return options

232

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

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

235

236 Returns:

237 ~ConsoleOptions: New console options instance.

238 """

239 options = self.copy()

240 options.height = None

241 return options

242

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

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

245

246 Args:

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

248 height (int): New height.

249

250 Returns:

251 ~ConsoleOptions: New console options instance.

252 """

253 options = self.copy()

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

255 options.height = options.max_height = height

256 return options

257

258

259@runtime_checkable

260 class RichCast(Protocol):

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

262

263 def __rich__(

264 self,

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

266 ...

267

268

269@runtime_checkable

270 class ConsoleRenderable(Protocol):

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

272

273 def __rich_console__(

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

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

276 ...

277

278

279# A type that may be rendered by Console.

280 RenderableType = Union[ConsoleRenderable, RichCast, str]

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

282

283# The result of calling a __rich_console__ method.

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

285

286 _null_highlighter = NullHighlighter()

287

288

289 class CaptureError(Exception):

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

291

292

293 class NewLine:

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

295

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

297 self.count = count

298

299 def __rich_console__(

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

301 ) -> Iterable[Segment]:

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

303

304

305 class ScreenUpdate:

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

307

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

309 self._lines = lines

310 self.x = x

311 self.y = y

312

313 def __rich_console__(

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

315 ) -> RenderResult:

316 x = self.x

317 move_to = Control.move_to

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

319 yield move_to(x, offset)

320 yield from line

321

322

323 class Capture:

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

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

326

327 Args:

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

329 """

330

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

332 self._console = console

333 self._result: Optional[str] = None

334

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

336 self._console.begin_capture()

337 return self

338

339 def __exit__(

340 self,

341 exc_type: Optional[Type[BaseException]],

342 exc_val: Optional[BaseException],

343 exc_tb: Optional[TracebackType],

344 ) -> None:

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

346

347 def get(self) -> str:

348 """Get the result of the capture."""

349 if self._result is None:

350 raise CaptureError(

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

352 )

353 return self._result

354

355

356 class ThemeContext:

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

358

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

360 self.console = console

361 self.theme = theme

362 self.inherit = inherit

363

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

365 self.console.push_theme(self.theme)

366 return self

367

368 def __exit__(

369 self,

370 exc_type: Optional[Type[BaseException]],

371 exc_val: Optional[BaseException],

372 exc_tb: Optional[TracebackType],

373 ) -> None:

374 self.console.pop_theme()

375

376

377 class PagerContext:

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

379

380 def __init__(

381 self,

382 console: "Console",

383 pager: Optional[Pager] = None,

384 styles: bool = False,

385 links: bool = False,

386 ) -> None:

387 self._console = console

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

389 self.styles = styles

390 self.links = links

391

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

393 self._console._enter_buffer()

394 return self

395

396 def __exit__(

397 self,

398 exc_type: Optional[Type[BaseException]],

399 exc_val: Optional[BaseException],

400 exc_tb: Optional[TracebackType],

401 ) -> None:

402 if exc_type is None:

403 with self._console._lock:

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

405 del self._console._buffer[:]

406 segments: Iterable[Segment] = buffer

407 if not self.styles:

408 segments = Segment.strip_styles(segments)

409 elif not self.links:

410 segments = Segment.strip_links(segments)

411 content = self._console._render_buffer(segments)

412 self.pager.show(content)

413 self._console._exit_buffer()

414

415

416 class ScreenContext:

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

418

419 def __init__(

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

421 ) -> None:

422 self.console = console

423 self.hide_cursor = hide_cursor

424 self.screen = Screen(style=style)

425 self._changed = False

426

427 def update(

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

429 ) -> None:

430 """Update the screen.

431

432 Args:

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

434 or None for no change. Defaults to None.

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

436 """

437 if renderables:

438 self.screen.renderable = (

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

440 )

441 if style is not None:

442 self.screen.style = style

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

444

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

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

447 if self._changed and self.hide_cursor:

448 self.console.show_cursor(False)

449 return self

450

451 def __exit__(

452 self,

453 exc_type: Optional[Type[BaseException]],

454 exc_val: Optional[BaseException],

455 exc_tb: Optional[TracebackType],

456 ) -> None:

457 if self._changed:

458 self.console.set_alt_screen(False)

459 if self.hide_cursor:

460 self.console.show_cursor(True)

461

462

463 class Group:

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

465

466 Args:

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

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

469 """

470

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

472 self._renderables = renderables

473 self.fit = fit

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

475

476 @property

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

478 if self._render is None:

479 self._render = list(self._renderables)

480 return self._render

481

482 def __rich_measure__(

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

484 ) -> "Measurement":

485 if self.fit:

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

487 else:

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

489

490 def __rich_console__(

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

492 ) -> RenderResult:

493 yield from self.renderables

494

495

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

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

498

499 Args:

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

501 """

502

503 def decorator(

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

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

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

507

508 @wraps(method)

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

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

511 return Group(*renderables, fit=fit)

512

513 return _replace

514

515 return decorator

516

517

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

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

520 try:

521 get_ipython # type: ignore[name-defined]

522 except NameError:

523 return False

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

525 shell = ipython.__class__.__name__

526 if (

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

528 or os.getenv("DATABRICKS_RUNTIME_VERSION")

529 or shell == "ZMQInteractiveShell"

530 ):

531 return True # Jupyter notebook or qtconsole

532 elif shell == "TerminalInteractiveShell":

533 return False # Terminal running IPython

534 else:

535 return False # Other type (?)

536

537

538 COLOR_SYSTEMS = {

539 "standard": ColorSystem.STANDARD,

540 "256": ColorSystem.EIGHT_BIT,

541 "truecolor": ColorSystem.TRUECOLOR,

542 "windows": ColorSystem.WINDOWS,

543}

544

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

546

547

548@dataclass

549 class ConsoleThreadLocals(threading.local):

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

551

552 theme_stack: ThemeStack

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

554 buffer_index: int = 0

555

556

557 class RenderHook(ABC):

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

559

560 @abstractmethod

561 def process_renderables(

562 self, renderables: List[ConsoleRenderable]

563 ) -> List[ConsoleRenderable]:

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

565

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

567

568 Args:

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

570

571 Returns:

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

573 """

574

575

576 _windows_console_features: Optional["WindowsConsoleFeatures"] = None

577

578

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

580 global _windows_console_features

581 if _windows_console_features is not None:

582 return _windows_console_features

583 from ._windows import get_windows_console_features

584

585 _windows_console_features = get_windows_console_features()

586 return _windows_console_features

587

588

589 def detect_legacy_windows() -> bool:

590 """Detect legacy Windows."""

591 return WINDOWS and not get_windows_console_features().vt

592

593

594 class Console:

595 """A high level console interface.

596

597 Args:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

622 highlighter (HighlighterType, optional): Default highlighter.

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

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

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

626 or None for datetime.now.

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

628 """

629

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

631

632 def __init__(

633 self,

634 *,

635 color_system: Optional[

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

637 ] = "auto",

638 force_terminal: Optional[bool] = None,

639 force_jupyter: Optional[bool] = None,

640 force_interactive: Optional[bool] = None,

641 soft_wrap: bool = False,

642 theme: Optional[Theme] = None,

643 stderr: bool = False,

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

645 quiet: bool = False,

646 width: Optional[int] = None,

647 height: Optional[int] = None,

648 style: Optional[StyleType] = None,

649 no_color: Optional[bool] = None,

650 tab_size: int = 8,

651 record: bool = False,

652 markup: bool = True,

653 emoji: bool = True,

654 emoji_variant: Optional[EmojiVariant] = None,

655 highlight: bool = True,

656 log_time: bool = True,

657 log_path: bool = True,

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

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

660 legacy_windows: Optional[bool] = None,

661 safe_box: bool = True,

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

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

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

665 ):

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

667 if _environ is not None:

668 self._environ = _environ

669

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

671 if self.is_jupyter:

672 if width is None:

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

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

675 width = int(jupyter_columns)

676 else:

677 width = JUPYTER_DEFAULT_COLUMNS

678 if height is None:

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

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

681 height = int(jupyter_lines)

682 else:

683 height = JUPYTER_DEFAULT_LINES

684

685 self.tab_size = tab_size

686 self.record = record

687 self._markup = markup

688 self._emoji = emoji

689 self._emoji_variant: Optional[EmojiVariant] = emoji_variant

690 self._highlight = highlight

691 self.legacy_windows: bool = (

692 (detect_legacy_windows() and not self.is_jupyter)

693 if legacy_windows is None

694 else legacy_windows

695 )

696

697 if width is None:

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

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

700 width = int(columns) - self.legacy_windows

701 if height is None:

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

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

704 height = int(lines)

705

706 self.soft_wrap = soft_wrap

707 self._width = width

708 self._height = height

709

710 self._color_system: Optional[ColorSystem]

711

712 self._force_terminal = None

713 if force_terminal is not None:

714 self._force_terminal = force_terminal

715

716 self._file = file

717 self.quiet = quiet

718 self.stderr = stderr

719

720 if color_system is None:

721 self._color_system = None

722 elif color_system == "auto":

723 self._color_system = self._detect_color_system()

724 else:

725 self._color_system = COLOR_SYSTEMS[color_system]

726

727 self._lock = threading.RLock()

728 self._log_render = LogRender(

729 show_time=log_time,

730 show_path=log_path,

731 time_format=log_time_format,

732 )

733 self.highlighter: HighlighterType = highlighter or _null_highlighter

734 self.safe_box = safe_box

735 self.get_datetime = get_datetime or datetime.now

736 self.get_time = get_time or monotonic

737 self.style = style

738 self.no_color = (

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

740 )

741 self.is_interactive = (

742 (self.is_terminal and not self.is_dumb_terminal)

743 if force_interactive is None

744 else force_interactive

745 )

746

747 self._record_buffer_lock = threading.RLock()

748 self._thread_locals = ConsoleThreadLocals(

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

750 )

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

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

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

754 self._is_alt_screen = False

755

756 def __repr__(self) -> str:

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

758

759 @property

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

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

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

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

764 if file is None:

765 file = NULL_FILE

766 return file

767

768 @file.setter

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

770 """Set a new file object."""

771 self._file = new_file

772

773 @property

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

775 """Get a thread local buffer."""

776 return self._thread_locals.buffer

777

778 @property

779 def _buffer_index(self) -> int:

780 """Get a thread local buffer."""

781 return self._thread_locals.buffer_index

782

783 @_buffer_index.setter

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

785 self._thread_locals.buffer_index = value

786

787 @property

788 def _theme_stack(self) -> ThemeStack:

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

790 return self._thread_locals.theme_stack

791

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

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

794 if self.is_jupyter:

795 return ColorSystem.TRUECOLOR

796 if not self.is_terminal or self.is_dumb_terminal:

797 return None

798 if WINDOWS: # pragma: no cover

799 if self.legacy_windows: # pragma: no cover

800 return ColorSystem.WINDOWS

801 windows_console_features = get_windows_console_features()

802 return (

803 ColorSystem.TRUECOLOR

804 if windows_console_features.truecolor

805 else ColorSystem.EIGHT_BIT

806 )

807 else:

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

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

810 return ColorSystem.TRUECOLOR

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

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

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

814 return color_system

815

816 def _enter_buffer(self) -> None:

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

818 self._buffer_index += 1

819

820 def _exit_buffer(self) -> None:

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

822 self._buffer_index -= 1

823 self._check_buffer()

824

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

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

827

828 Args:

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

830

831 Raises:

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

833 """

834 with self._lock:

835 if self._live is not None:

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

837 self._live = live

838

839 def clear_live(self) -> None:

840 """Clear the Live instance."""

841 with self._lock:

842 self._live = None

843

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

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

846

847 Args:

848 hook (RenderHook): Render hook instance.

849 """

850 with self._lock:

851 self._render_hooks.append(hook)

852

853 def pop_render_hook(self) -> None:

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

855 with self._lock:

856 self._render_hooks.pop()

857

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

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

860 self._enter_buffer()

861 return self

862

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

864 """Exit buffer context."""

865 self._exit_buffer()

866

867 def begin_capture(self) -> None:

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

869 self._enter_buffer()

870

871 def end_capture(self) -> str:

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

873

874 Returns:

875 str: Console output.

876 """

877 render_result = self._render_buffer(self._buffer)

878 del self._buffer[:]

879 self._exit_buffer()

880 return render_result

881

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

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

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

885 than calling this method directly.

886

887 Args:

888 theme (Theme): A theme instance.

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

890 """

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

892

893 def pop_theme(self) -> None:

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

895 self._theme_stack.pop_theme()

896

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

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

899

900 Args:

901 theme (Theme): Theme instance to user.

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

903

904 Returns:

905 ThemeContext: [description]

906 """

907 return ThemeContext(self, theme, inherit)

908

909 @property

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

911 """Get color system string.

912

913 Returns:

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

915 """

916

917 if self._color_system is not None:

918 return _COLOR_SYSTEMS_NAMES[self._color_system]

919 else:

920 return None

921

922 @property

923 def encoding(self) -> str:

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

925

926 Returns:

927 str: A standard encoding string.

928 """

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

930

931 @property

932 def is_terminal(self) -> bool:

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

934

935 Returns:

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

937 understanding terminal codes, otherwise False.

938 """

939 if self._force_terminal is not None:

940 return self._force_terminal

941

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

943 "idlelib"

944 ):

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

946 return False

947

948 if self.is_jupyter:

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

950 return False

951

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

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

954 if force_color is not None:

955 self._force_terminal = True

956 return True

957

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

959 try:

960 return False if isatty is None else isatty()

961 except ValueError:

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

963 # ValueError: I/O operation on closed file

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

965 return False

966

967 @property

968 def is_dumb_terminal(self) -> bool:

969 """Detect dumb terminal.

970

971 Returns:

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

973

974 """

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

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

977 return self.is_terminal and is_dumb

978

979 @property

980 def options(self) -> ConsoleOptions:

981 """Get default console options."""

982 return ConsoleOptions(

983 max_height=self.size.height,

984 size=self.size,

985 legacy_windows=self.legacy_windows,

986 min_width=1,

987 max_width=self.width,

988 encoding=self.encoding,

989 is_terminal=self.is_terminal,

990 )

991

992 @property

993 def size(self) -> ConsoleDimensions:

994 """Get the size of the console.

995

996 Returns:

997 ConsoleDimensions: A named tuple containing the dimensions.

998 """

999

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

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

1002

1003 if self.is_dumb_terminal:

1004 return ConsoleDimensions(80, 25)

1005

1006 width: Optional[int] = None

1007 height: Optional[int] = None

1008

1009 if WINDOWS: # pragma: no cover

1010 try:

1011 width, height = os.get_terminal_size()

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

1013 pass

1014 else:

1015 for file_descriptor in _STD_STREAMS:

1016 try:

1017 width, height = os.get_terminal_size(file_descriptor)

1018 except (AttributeError, ValueError, OSError):

1019 pass

1020 else:

1021 break

1022

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

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

1025 width = int(columns)

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

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

1028 height = int(lines)

1029

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

1031 width = width or 80

1032 height = height or 25

1033 return ConsoleDimensions(

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

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

1036 )

1037

1038 @size.setter

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

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

1041

1042 Args:

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

1044 """

1045 width, height = new_size

1046 self._width = width

1047 self._height = height

1048

1049 @property

1050 def width(self) -> int:

1051 """Get the width of the console.

1052

1053 Returns:

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

1055 """

1056 return self.size.width

1057

1058 @width.setter

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

1060 """Set width.

1061

1062 Args:

1063 width (int): New width.

1064 """

1065 self._width = width

1066

1067 @property

1068 def height(self) -> int:

1069 """Get the height of the console.

1070

1071 Returns:

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

1073 """

1074 return self.size.height

1075

1076 @height.setter

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

1078 """Set height.

1079

1080 Args:

1081 height (int): new height.

1082 """

1083 self._height = height

1084

1085 def bell(self) -> None:

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

1087 self.control(Control.bell())

1088

1089 def capture(self) -> Capture:

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

1091 rather than writing it to the console.

1092

1093 Example:

1094 >>> from rich.console import Console

1095 >>> console = Console()

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

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

1098 >>> print(capture.get())

1099

1100 Returns:

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

1102 """

1103 capture = Capture(self)

1104 return capture

1105

1106 def pager(

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

1108 ) -> PagerContext:

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

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

1111

1112 Args:

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

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

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

1116

1117 Example:

1118 >>> from rich.console import Console

1119 >>> from rich.__main__ import make_test_card

1120 >>> console = Console()

1121 >>> with console.pager():

1122 console.print(make_test_card())

1123

1124 Returns:

1125 PagerContext: A context manager.

1126 """

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

1128

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

1130 """Write new line(s).

1131

1132 Args:

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

1134 """

1135

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

1137 self.print(NewLine(count))

1138

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

1140 """Clear the screen.

1141

1142 Args:

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

1144 """

1145 if home:

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

1147 else:

1148 self.control(Control.clear())

1149

1150 def status(

1151 self,

1152 status: RenderableType,

1153 *,

1154 spinner: str = "dots",

1155 spinner_style: StyleType = "status.spinner",

1156 speed: float = 1.0,

1157 refresh_per_second: float = 12.5,

1158 ) -> "Status":

1159 """Display a status and spinner.

1160

1161 Args:

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

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

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

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

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

1167

1168 Returns:

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

1170 """

1171 from .status import Status

1172

1173 status_renderable = Status(

1174 status,

1175 console=self,

1176 spinner=spinner,

1177 spinner_style=spinner_style,

1178 speed=speed,

1179 refresh_per_second=refresh_per_second,

1180 )

1181 return status_renderable

1182

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

1184 """Show or hide the cursor.

1185

1186 Args:

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

1188 """

1189 if self.is_terminal:

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

1191 return True

1192 return False

1193

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

1195 """Enables alternative screen mode.

1196

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

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

1199 that handles this for you.

1200

1201 Args:

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

1203

1204 Returns:

1205 bool: True if the control codes were written.

1206

1207 """

1208 changed = False

1209 if self.is_terminal and not self.legacy_windows:

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

1211 changed = True

1212 self._is_alt_screen = enable

1213 return changed

1214

1215 @property

1216 def is_alt_screen(self) -> bool:

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

1218

1219 Returns:

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

1221 """

1222 return self._is_alt_screen

1223

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

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

1226

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

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

1229 exits.

1230

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

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

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

1234

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

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

1237

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

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

1240 using this method being overwritten.

1241

1242 Args:

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

1244

1245 Returns:

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

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

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

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

1250 """

1251 if self.is_terminal:

1252 self.control(Control.title(title))

1253 return True

1254 return False

1255

1256 def screen(

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

1258 ) -> "ScreenContext":

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

1260

1261 Args:

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

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

1264

1265 Returns:

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

1267 """

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

1269

1270 def measure(

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

1272 ) -> Measurement:

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

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

1275

1276 Args:

1277 renderable (RenderableType): Any renderable or string.

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

1279 to use default options. Defaults to None.

1280

1281 Returns:

1282 Measurement: A measurement of the renderable.

1283 """

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

1285 return measurement

1286

1287 def render(

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

1289 ) -> Iterable[Segment]:

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

1291

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

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

1294

1295 Args:

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

1297 an object that may be converted to a string.

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

1299

1300 Returns:

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

1302 """

1303

1304 _options = options or self.options

1305 if _options.max_width < 1:

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

1307 return

1308 render_iterable: RenderResult

1309

1310 renderable = rich_cast(renderable)

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

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

1313 elif isinstance(renderable, str):

1314 text_renderable = self.render_str(

1315 renderable, highlight=_options.highlight, markup=_options.markup

1316 )

1317 render_iterable = text_renderable.__rich_console__(self, _options)

1318 else:

1319 raise errors.NotRenderableError(

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

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

1322 )

1323

1324 try:

1325 iter_render = iter(render_iterable)

1326 except TypeError:

1327 raise errors.NotRenderableError(

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

1329 )

1330 _Segment = Segment

1331 _options = _options.reset_height()

1332 for render_output in iter_render:

1333 if isinstance(render_output, _Segment):

1334 yield render_output

1335 else:

1336 yield from self.render(render_output, _options)

1337

1338 def render_lines(

1339 self,

1340 renderable: RenderableType,

1341 options: Optional[ConsoleOptions] = None,

1342 *,

1343 style: Optional[Style] = None,

1344 pad: bool = True,

1345 new_lines: bool = False,

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

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

1348

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

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

1351

1352 Args:

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

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

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

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

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

1358

1359 Returns:

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

1361 """

1362 with self._lock:

1363 render_options = options or self.options

1364 _rendered = self.render(renderable, render_options)

1365 if style:

1366 _rendered = Segment.apply_style(_rendered, style)

1367

1368 render_height = render_options.height

1369 if render_height is not None:

1370 render_height = max(0, render_height)

1371

1372 lines = list(

1373 islice(

1374 Segment.split_and_crop_lines(

1375 _rendered,

1376 render_options.max_width,

1377 include_new_lines=new_lines,

1378 pad=pad,

1379 style=style,

1380 ),

1381 None,

1382 render_height,

1383 )

1384 )

1385 if render_options.height is not None:

1386 extra_lines = render_options.height - len(lines)

1387 if extra_lines > 0:

1388 pad_line = [

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

1390 if new_lines

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

1392 ]

1393 lines.extend(pad_line * extra_lines)

1394

1395 return lines

1396

1397 def render_str(

1398 self,

1399 text: str,

1400 *,

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

1402 justify: Optional[JustifyMethod] = None,

1403 overflow: Optional[OverflowMethod] = None,

1404 emoji: Optional[bool] = None,

1405 markup: Optional[bool] = None,

1406 highlight: Optional[bool] = None,

1407 highlighter: Optional[HighlighterType] = None,

1408 ) -> "Text":

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

1410 you print or log a string.

1411

1412 Args:

1413 text (str): Text to render.

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

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

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

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

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

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

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

1421 Returns:

1422 ConsoleRenderable: Renderable object.

1423

1424 """

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

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

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

1428

1429 if markup_enabled:

1430 rich_text = render_markup(

1431 text,

1432 style=style,

1433 emoji=emoji_enabled,

1434 emoji_variant=self._emoji_variant,

1435 )

1436 rich_text.justify = justify

1437 rich_text.overflow = overflow

1438 else:

1439 rich_text = Text(

1440 _emoji_replace(text, default_variant=self._emoji_variant)

1441 if emoji_enabled

1442 else text,

1443 justify=justify,

1444 overflow=overflow,

1445 style=style,

1446 )

1447

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

1449 if _highlighter is not None:

1450 highlight_text = _highlighter(str(rich_text))

1451 highlight_text.copy_styles(rich_text)

1452 return highlight_text

1453

1454 return rich_text

1455

1456 def get_style(

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

1458 ) -> Style:

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

1460

1461 Args:

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

1463

1464 Returns:

1465 Style: A Style object.

1466

1467 Raises:

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

1469

1470 """

1471 if isinstance(name, Style):

1472 return name

1473

1474 try:

1475 style = self._theme_stack.get(name)

1476 if style is None:

1477 style = Style.parse(name)

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

1479 except errors.StyleSyntaxError as error:

1480 if default is not None:

1481 return self.get_style(default)

1482 raise errors.MissingStyle(

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

1484 ) from None

1485

1486 def _collect_renderables(

1487 self,

1488 objects: Iterable[Any],

1489 sep: str,

1490 end: str,

1491 *,

1492 justify: Optional[JustifyMethod] = None,

1493 emoji: Optional[bool] = None,

1494 markup: Optional[bool] = None,

1495 highlight: Optional[bool] = None,

1496 ) -> List[ConsoleRenderable]:

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

1498

1499 Args:

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

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

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

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

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

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

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

1507

1508 Returns:

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

1510 """

1511 renderables: List[ConsoleRenderable] = []

1512 _append = renderables.append

1513 text: List[Text] = []

1514 append_text = text.append

1515

1516 append = _append

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

1518

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

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

1521

1522 append = align_append

1523

1524 _highlighter: HighlighterType = _null_highlighter

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

1526 _highlighter = self.highlighter

1527

1528 def check_text() -> None:

1529 if text:

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

1531 append(sep_text.join(text))

1532 text.clear()

1533

1534 for renderable in objects:

1535 renderable = rich_cast(renderable)

1536 if isinstance(renderable, str):

1537 append_text(

1538 self.render_str(

1539 renderable, emoji=emoji, markup=markup, highlighter=_highlighter

1540 )

1541 )

1542 elif isinstance(renderable, Text):

1543 append_text(renderable)

1544 elif isinstance(renderable, ConsoleRenderable):

1545 check_text()

1546 append(renderable)

1547 elif is_expandable(renderable):

1548 check_text()

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

1550 else:

1551 append_text(_highlighter(str(renderable)))

1552

1553 check_text()

1554

1555 if self.style is not None:

1556 style = self.get_style(self.style)

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

1558

1559 return renderables

1560

1561 def rule(

1562 self,

1563 title: TextType = "",

1564 *,

1565 characters: str = "─",

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

1567 align: AlignMethod = "center",

1568 ) -> None:

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

1570

1571 Args:

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

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

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

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

1576 """

1577 from .rule import Rule

1578

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

1580 self.print(rule)

1581

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

1583 """Insert non-printing control codes.

1584

1585 Args:

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

1587 """

1588 if not self.is_dumb_terminal:

1589 with self:

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

1591

1592 def out(

1593 self,

1594 *objects: Any,

1595 sep: str = " ",

1596 end: str = "\n",

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

1598 highlight: Optional[bool] = None,

1599 ) -> None:

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

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

1602 optionally apply highlighting and a basic style.

1603

1604 Args:

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

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

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

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

1609 console default. Defaults to ``None``.

1610 """

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

1612 self.print(

1613 raw_output,

1614 style=style,

1615 highlight=highlight,

1616 emoji=False,

1617 markup=False,

1618 no_wrap=True,

1619 overflow="ignore",

1620 crop=False,

1621 end=end,

1622 )

1623

1624 def print(

1625 self,

1626 *objects: Any,

1627 sep: str = " ",

1628 end: str = "\n",

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

1630 justify: Optional[JustifyMethod] = None,

1631 overflow: Optional[OverflowMethod] = None,

1632 no_wrap: Optional[bool] = None,

1633 emoji: Optional[bool] = None,

1634 markup: Optional[bool] = None,

1635 highlight: Optional[bool] = None,

1636 width: Optional[int] = None,

1637 height: Optional[int] = None,

1638 crop: bool = True,

1639 soft_wrap: Optional[bool] = None,

1640 new_line_start: bool = False,

1641 ) -> None:

1642 """Print to the console.

1643

1644 Args:

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

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

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

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

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

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

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

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

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

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

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

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

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

1658 Console default. Defaults to ``None``.

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

1660 """

1661 if not objects:

1662 objects = (NewLine(),)

1663

1664 if soft_wrap is None:

1665 soft_wrap = self.soft_wrap

1666 if soft_wrap:

1667 if no_wrap is None:

1668 no_wrap = True

1669 if overflow is None:

1670 overflow = "ignore"

1671 crop = False

1672 render_hooks = self._render_hooks[:]

1673 with self:

1674 renderables = self._collect_renderables(

1675 objects,

1676 sep,

1677 end,

1678 justify=justify,

1679 emoji=emoji,

1680 markup=markup,

1681 highlight=highlight,

1682 )

1683 for hook in render_hooks:

1684 renderables = hook.process_renderables(renderables)

1685 render_options = self.options.update(

1686 justify=justify,

1687 overflow=overflow,

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

1689 height=height,

1690 no_wrap=no_wrap,

1691 markup=markup,

1692 highlight=highlight,

1693 )

1694

1695 new_segments: List[Segment] = []

1696 extend = new_segments.extend

1697 render = self.render

1698 if style is None:

1699 for renderable in renderables:

1700 extend(render(renderable, render_options))

1701 else:

1702 for renderable in renderables:

1703 extend(

1704 Segment.apply_style(

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

1706 )

1707 )

1708 if new_line_start:

1709 if (

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

1711 > 1

1712 ):

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

1714 if crop:

1715 buffer_extend = self._buffer.extend

1716 for line in Segment.split_and_crop_lines(

1717 new_segments, self.width, pad=False

1718 ):

1719 buffer_extend(line)

1720 else:

1721 self._buffer.extend(new_segments)

1722

1723 def print_json(

1724 self,

1725 json: Optional[str] = None,

1726 *,

1727 data: Any = None,

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

1729 highlight: bool = True,

1730 skip_keys: bool = False,

1731 ensure_ascii: bool = False,

1732 check_circular: bool = True,

1733 allow_nan: bool = True,

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

1735 sort_keys: bool = False,

1736 ) -> None:

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

1738

1739 Args:

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

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

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

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

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

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

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

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

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

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

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

1751 """

1752 from rich.json import JSON

1753

1754 if json is None:

1755 json_renderable = JSON.from_data(

1756 data,

1757 indent=indent,

1758 highlight=highlight,

1759 skip_keys=skip_keys,

1760 ensure_ascii=ensure_ascii,

1761 check_circular=check_circular,

1762 allow_nan=allow_nan,

1763 default=default,

1764 sort_keys=sort_keys,

1765 )

1766 else:

1767 if not isinstance(json, str):

1768 raise TypeError(

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

1770 )

1771 json_renderable = JSON(

1772 json,

1773 indent=indent,

1774 highlight=highlight,

1775 skip_keys=skip_keys,

1776 ensure_ascii=ensure_ascii,

1777 check_circular=check_circular,

1778 allow_nan=allow_nan,

1779 default=default,

1780 sort_keys=sort_keys,

1781 )

1782 self.print(json_renderable, soft_wrap=True)

1783

1784 def update_screen(

1785 self,

1786 renderable: RenderableType,

1787 *,

1788 region: Optional[Region] = None,

1789 options: Optional[ConsoleOptions] = None,

1790 ) -> None:

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

1792

1793 Args:

1794 renderable (RenderableType): A Rich renderable.

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

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

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

1798

1799 Raises:

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

1801

1802 """

1803 if not self.is_alt_screen:

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

1805 render_options = options or self.options

1806 if region is None:

1807 x = y = 0

1808 render_options = render_options.update_dimensions(

1809 render_options.max_width, render_options.height or self.height

1810 )

1811 else:

1812 x, y, width, height = region

1813 render_options = render_options.update_dimensions(width, height)

1814

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

1816 self.update_screen_lines(lines, x, y)

1817

1818 def update_screen_lines(

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

1820 ) -> None:

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

1822

1823 Args:

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

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

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

1827

1828 Raises:

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

1830 """

1831 if not self.is_alt_screen:

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

1833 screen_update = ScreenUpdate(lines, x, y)

1834 segments = self.render(screen_update)

1835 self._buffer.extend(segments)

1836 self._check_buffer()

1837

1838 def print_exception(

1839 self,

1840 *,

1841 width: Optional[int] = 100,

1842 extra_lines: int = 3,

1843 theme: Optional[str] = None,

1844 word_wrap: bool = False,

1845 show_locals: bool = False,

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

1847 max_frames: int = 100,

1848 ) -> None:

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

1850

1851 Args:

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

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

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

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

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

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

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

1859 """

1860 from .traceback import Traceback

1861

1862 traceback = Traceback(

1863 width=width,

1864 extra_lines=extra_lines,

1865 theme=theme,

1866 word_wrap=word_wrap,

1867 show_locals=show_locals,

1868 suppress=suppress,

1869 max_frames=max_frames,

1870 )

1871 self.print(traceback)

1872

1873 @staticmethod

1874 def _caller_frame_info(

1875 offset: int,

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

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

1878 """Get caller frame information.

1879

1880 Args:

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

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

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

1884

1885 Returns:

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

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

1888

1889 Raises:

1890 RuntimeError: If the stack offset is invalid.

1891 """

1892 # Ignore the frame of this local helper

1893 offset += 1

1894

1895 frame = currentframe()

1896 if frame is not None:

1897 # Use the faster currentframe where implemented

1898 while offset and frame is not None:

1899 frame = frame.f_back

1900 offset -= 1

1901 assert frame is not None

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

1903 else:

1904 # Fallback to the slower stack

1905 frame_info = inspect.stack()[offset]

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

1907

1908 def log(

1909 self,

1910 *objects: Any,

1911 sep: str = " ",

1912 end: str = "\n",

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

1914 justify: Optional[JustifyMethod] = None,

1915 emoji: Optional[bool] = None,

1916 markup: Optional[bool] = None,

1917 highlight: Optional[bool] = None,

1918 log_locals: bool = False,

1919 _stack_offset: int = 1,

1920 ) -> None:

1921 """Log rich content to the terminal.

1922

1923 Args:

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

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

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

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

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

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

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

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

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

1933 was called. Defaults to False.

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

1935 """

1936 if not objects:

1937 objects = (NewLine(),)

1938

1939 render_hooks = self._render_hooks[:]

1940

1941 with self:

1942 renderables = self._collect_renderables(

1943 objects,

1944 sep,

1945 end,

1946 justify=justify,

1947 emoji=emoji,

1948 markup=markup,

1949 highlight=highlight,

1950 )

1951 if style is not None:

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

1953

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

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

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

1957 if log_locals:

1958 locals_map = {

1959 key: value

1960 for key, value in locals.items()

1961 if not key.startswith("__")

1962 }

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

1964

1965 renderables = [

1966 self._log_render(

1967 self,

1968 renderables,

1969 log_time=self.get_datetime(),

1970 path=path,

1971 line_no=line_no,

1972 link_path=link_path,

1973 )

1974 ]

1975 for hook in render_hooks:

1976 renderables = hook.process_renderables(renderables)

1977 new_segments: List[Segment] = []

1978 extend = new_segments.extend

1979 render = self.render

1980 render_options = self.options

1981 for renderable in renderables:

1982 extend(render(renderable, render_options))

1983 buffer_extend = self._buffer.extend

1984 for line in Segment.split_and_crop_lines(

1985 new_segments, self.width, pad=False

1986 ):

1987 buffer_extend(line)

1988

1989 def _check_buffer(self) -> None:

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

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

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

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

1994 """

1995 if self.quiet:

1996 del self._buffer[:]

1997 return

1998 with self._lock:

1999 if self.record:

2000 with self._record_buffer_lock:

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

2002

2003 if self._buffer_index == 0:

2004 if self.is_jupyter: # pragma: no cover

2005 from .jupyter import display

2006

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

2008 del self._buffer[:]

2009 else:

2010 if WINDOWS:

2011 use_legacy_windows_render = False

2012 if self.legacy_windows:

2013 fileno = get_fileno(self.file)

2014 if fileno is not None:

2015 use_legacy_windows_render = (

2016 fileno in _STD_STREAMS_OUTPUT

2017 )

2018

2019 if use_legacy_windows_render:

2020 from rich._win32_console import LegacyWindowsTerm

2021 from 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 rich.cells import cell_len

2306

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

2308

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

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

2311 if style in style_cache:

2312 return style_cache[style]

2313 css_rules = []

2314 color = (

2315 _theme.foreground_color

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

2317 else style.color.get_truecolor(_theme)

2318 )

2319 bgcolor = (

2320 _theme.background_color

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

2322 else style.bgcolor.get_truecolor(_theme)

2323 )

2324 if style.reverse:

2325 color, bgcolor = bgcolor, color

2326 if style.dim:

2327 color = blend_rgb(color, bgcolor, 0.4)

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

2329 if style.bold:

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

2331 if style.italic:

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

2333 if style.underline:

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

2335 if style.strike:

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

2337

2338 css = ";".join(css_rules)

2339 style_cache[style] = css

2340 return css

2341

2342 _theme = theme or SVG_EXPORT_THEME

2343

2344 width = self.width

2345 char_height = 20

2346 char_width = char_height * font_aspect_ratio

2347 line_height = char_height * 1.22

2348

2349 margin_top = 1

2350 margin_right = 1

2351 margin_bottom = 1

2352 margin_left = 1

2353

2354 padding_top = 40

2355 padding_right = 8

2356 padding_bottom = 8

2357 padding_left = 8

2358

2359 padding_width = padding_left + padding_right

2360 padding_height = padding_top + padding_bottom

2361 margin_width = margin_left + margin_right

2362 margin_height = margin_top + margin_bottom

2363

2364 text_backgrounds: List[str] = []

2365 text_group: List[str] = []

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

2367 style_no = 1

2368

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

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

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

2372

2373 def make_tag(

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

2375 ) -> str:

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

2377

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

2379 if isinstance(value, (float)):

2380 return format(value, "g")

2381 return str(value)

2382

2383 tag_attribs = " ".join(

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

2385 for k, v in attribs.items()

2386 )

2387 return (

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

2389 if content

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

2391 )

2392

2393 with self._record_buffer_lock:

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

2395 if clear:

2396 self._record_buffer.clear()

2397

2398 if unique_id is None:

2399 unique_id = "terminal-" + str(

2400 zlib.adler32(

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

2402 "utf-8",

2403 "ignore",

2404 )

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

2406 )

2407 )

2408 y = 0

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

2410 x = 0

2411 for text, style, _control in line:

2412 style = style or Style()

2413 rules = get_svg_style(style)

2414 if rules not in classes:

2415 classes[rules] = style_no

2416 style_no += 1

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

2418

2419 if style.reverse:

2420 has_background = True

2421 background = (

2422 _theme.foreground_color.hex

2423 if style.color is None

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

2425 )

2426 else:

2427 bgcolor = style.bgcolor

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

2429 background = (

2430 _theme.background_color.hex

2431 if style.bgcolor is None

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

2433 )

2434

2435 text_length = cell_len(text)

2436 if has_background:

2437 text_backgrounds.append(

2438 make_tag(

2439 "rect",

2440 fill=background,

2441 x=x * char_width,

2442 y=y * line_height + 1.5,

2443 width=char_width * text_length,

2444 height=line_height + 0.25,

2445 shape_rendering="crispEdges",

2446 )

2447 )

2448

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

2450 text_group.append(

2451 make_tag(

2452 "text",

2453 escape_text(text),

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

2455 x=x * char_width,

2456 y=y * line_height + char_height,

2457 textLength=char_width * len(text),

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

2459 )

2460 )

2461 x += cell_len(text)

2462

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

2464 lines = "\n".join(

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

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

2467 </clipPath>"""

2468 for line_no, offset in enumerate(line_offsets)

2469 )

2470

2471 styles = "\n".join(

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

2473 )

2474 backgrounds = "".join(text_backgrounds)

2475 matrix = "".join(text_group)

2476

2477 terminal_width = ceil(width * char_width + padding_width)

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

2479 chrome = make_tag(

2480 "rect",

2481 fill=_theme.background_color.hex,

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

2483 stroke_width="1",

2484 x=margin_left,

2485 y=margin_top,

2486 width=terminal_width,

2487 height=terminal_height,

2488 rx=8,

2489 )

2490

2491 title_color = _theme.foreground_color.hex

2492 if title:

2493 chrome += make_tag(

2494 "text",

2495 escape_text(title),

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

2497 fill=title_color,

2498 text_anchor="middle",

2499 x=terminal_width // 2,

2500 y=margin_top + char_height + 6,

2501 )

2502 chrome += f"""

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

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

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

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

2507 </g>

2508 """

2509

2510 svg = code_format.format(

2511 unique_id=unique_id,

2512 char_width=char_width,

2513 char_height=char_height,

2514 line_height=line_height,

2515 terminal_width=char_width * width - 1,

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

2517 width=terminal_width + margin_width,

2518 height=terminal_height + margin_height,

2519 terminal_x=margin_left + padding_left,

2520 terminal_y=margin_top + padding_top,

2521 styles=styles,

2522 chrome=chrome,

2523 backgrounds=backgrounds,

2524 matrix=matrix,

2525 lines=lines,

2526 )

2527 return svg

2528

2529 def save_svg(

2530 self,

2531 path: str,

2532 *,

2533 title: str = "Rich",

2534 theme: Optional[TerminalTheme] = None,

2535 clear: bool = True,

2536 code_format: str = CONSOLE_SVG_FORMAT,

2537 font_aspect_ratio: float = 0.61,

2538 unique_id: Optional[str] = None,

2539 ) -> None:

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

2541

2542 Args:

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

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

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

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

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

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

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

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

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

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

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

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

2555 """

2556 svg = self.export_svg(

2557 title=title,

2558 theme=theme,

2559 clear=clear,

2560 code_format=code_format,

2561 font_aspect_ratio=font_aspect_ratio,

2562 unique_id=unique_id,

2563 )

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

2565 write_file.write(svg)

2566

2567

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

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

2570

2571 Args:

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

2573

2574 Returns:

2575 str: a hash of the given content

2576 """

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

2578

2579

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

2581 console = Console(record=True)

2582

2583 console.log(

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

2585 5,

2586 1.3,

2587 True,

2588 False,

2589 None,

2590 {

2591 "jsonrpc": "2.0",

2592 "method": "subtract",

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

2594 "id": 3,

2595 },

2596 )

2597

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

2599

2600 console.print(

2601 {

2602 "name": None,

2603 "empty": [],

2604 "quiz": {

2605 "sport": {

2606 "answered": True,

2607 "q1": {

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

2609 "options": [

2610 "New York Bulls",

2611 "Los Angeles Kings",

2612 "Golden State Warriors",

2613 "Huston Rocket",

2614 ],

2615 "answer": "Huston Rocket",

2616 },

2617 },

2618 "maths": {

2619 "answered": False,

2620 "q1": {

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

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

2623 "answer": 12,

2624 },

2625 "q2": {

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

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

2628 "answer": 4,

2629 },

2630 },

2631 },

2632 }

2633 )