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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

333 statements  

1from enum import IntEnum 

2from functools import lru_cache 

3from itertools import filterfalse 

4from logging import getLogger 

5from operator import attrgetter 

6from typing import ( 

7 TYPE_CHECKING, 

8 Dict, 

9 Iterable, 

10 List, 

11 NamedTuple, 

12 Optional, 

13 Sequence, 

14 Tuple, 

15 Type, 

16 Union, 

17) 

18 

19from .cells import ( 

20 _is_single_cell_widths, 

21 cached_cell_len, 

22 cell_len, 

23 get_character_cell_size, 

24 set_cell_size, 

25) 

26from .repr import Result, rich_repr 

27from .style import Style 

28 

29if TYPE_CHECKING: 

30 from .console import Console, ConsoleOptions, RenderResult 

31 

32log = getLogger("rich") 

33 

34 

35class ControlType(IntEnum): 

36 """Non-printable control codes which typically translate to ANSI codes.""" 

37 

38 BELL = 1 

39 CARRIAGE_RETURN = 2 

40 HOME = 3 

41 CLEAR = 4 

42 SHOW_CURSOR = 5 

43 HIDE_CURSOR = 6 

44 ENABLE_ALT_SCREEN = 7 

45 DISABLE_ALT_SCREEN = 8 

46 CURSOR_UP = 9 

47 CURSOR_DOWN = 10 

48 CURSOR_FORWARD = 11 

49 CURSOR_BACKWARD = 12 

50 CURSOR_MOVE_TO_COLUMN = 13 

51 CURSOR_MOVE_TO = 14 

52 ERASE_IN_LINE = 15 

53 SET_WINDOW_TITLE = 16 

54 

55 

56ControlCode = Union[ 

57 Tuple[ControlType], 

58 Tuple[ControlType, Union[int, str]], 

59 Tuple[ControlType, int, int], 

60] 

61 

62 

63@rich_repr() 

64class Segment(NamedTuple): 

65 """A piece of text with associated style. Segments are produced by the Console render process and 

66 are ultimately converted in to strings to be written to the terminal. 

67 

68 Args: 

69 text (str): A piece of text. 

70 style (:class:`~rich.style.Style`, optional): An optional style to apply to the text. 

71 control (Tuple[ControlCode], optional): Optional sequence of control codes. 

72 

73 Attributes: 

74 cell_length (int): The cell length of this Segment. 

75 """ 

76 

77 text: str 

78 style: Optional[Style] = None 

79 control: Optional[Sequence[ControlCode]] = None 

80 

81 @property 

82 def cell_length(self) -> int: 

83 """The number of terminal cells required to display self.text. 

84 

85 Returns: 

86 int: A number of cells. 

87 """ 

88 text, _style, control = self 

89 return 0 if control else cell_len(text) 

90 

91 def __rich_repr__(self) -> Result: 

92 yield self.text 

93 if self.control is None: 

94 if self.style is not None: 

95 yield self.style 

96 else: 

97 yield self.style 

98 yield self.control 

99 

100 def __bool__(self) -> bool: 

101 """Check if the segment contains text.""" 

102 return bool(self.text) 

103 

104 @property 

105 def is_control(self) -> bool: 

106 """Check if the segment contains control codes.""" 

107 return self.control is not None 

108 

109 @classmethod 

110 @lru_cache(1024 * 16) 

111 def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]: 

112 """Split a segment in to two at a given cell position. 

113 

114 Note that splitting a double-width character, may result in that character turning 

115 into two spaces. 

116 

117 Args: 

118 segment (Segment): A segment to split. 

119 cut (int): A cell position to cut on. 

120 

121 Returns: 

122 A tuple of two segments. 

123 """ 

124 text, style, control = segment 

125 _Segment = Segment 

126 cell_length = segment.cell_length 

127 if cut >= cell_length: 

128 return segment, _Segment("", style, control) 

129 

130 cell_size = get_character_cell_size 

131 

132 pos = int((cut / cell_length) * len(text)) 

133 

134 while True: 

135 before = text[:pos] 

136 cell_pos = cell_len(before) 

137 out_by = cell_pos - cut 

138 if not out_by: 

139 return ( 

140 _Segment(before, style, control), 

141 _Segment(text[pos:], style, control), 

142 ) 

143 if out_by == -1 and cell_size(text[pos]) == 2: 

144 return ( 

145 _Segment(text[:pos] + " ", style, control), 

146 _Segment(" " + text[pos + 1 :], style, control), 

147 ) 

148 if out_by == +1 and cell_size(text[pos - 1]) == 2: 

149 return ( 

150 _Segment(text[: pos - 1] + " ", style, control), 

151 _Segment(" " + text[pos:], style, control), 

152 ) 

153 if cell_pos < cut: 

154 pos += 1 

155 else: 

156 pos -= 1 

157 

158 def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]: 

159 """Split segment in to two segments at the specified column. 

160 

161 If the cut point falls in the middle of a 2-cell wide character then it is replaced 

162 by two spaces, to preserve the display width of the parent segment. 

163 

164 Args: 

165 cut (int): Offset within the segment to cut. 

166 

167 Returns: 

168 Tuple[Segment, Segment]: Two segments. 

169 """ 

170 text, style, control = self 

171 assert cut >= 0 

172 

173 if _is_single_cell_widths(text): 

174 # Fast path with all 1 cell characters 

175 if cut >= len(text): 

176 return self, Segment("", style, control) 

177 return ( 

178 Segment(text[:cut], style, control), 

179 Segment(text[cut:], style, control), 

180 ) 

181 

182 return self._split_cells(self, cut) 

183 

184 @classmethod 

185 def line(cls) -> "Segment": 

186 """Make a new line segment.""" 

187 return cls("\n") 

188 

189 @classmethod 

190 def apply_style( 

191 cls, 

192 segments: Iterable["Segment"], 

193 style: Optional[Style] = None, 

194 post_style: Optional[Style] = None, 

195 ) -> Iterable["Segment"]: 

196 """Apply style(s) to an iterable of segments. 

197 

198 Returns an iterable of segments where the style is replaced by ``style + segment.style + post_style``. 

199 

200 Args: 

201 segments (Iterable[Segment]): Segments to process. 

202 style (Style, optional): Base style. Defaults to None. 

203 post_style (Style, optional): Style to apply on top of segment style. Defaults to None. 

204 

205 Returns: 

206 Iterable[Segments]: A new iterable of segments (possibly the same iterable). 

207 """ 

208 result_segments = segments 

209 if style: 

210 apply = style.__add__ 

211 result_segments = ( 

212 cls(text, None if control else apply(_style), control) 

213 for text, _style, control in result_segments 

214 ) 

215 if post_style: 

216 result_segments = ( 

217 cls( 

218 text, 

219 ( 

220 None 

221 if control 

222 else (_style + post_style if _style else post_style) 

223 ), 

224 control, 

225 ) 

226 for text, _style, control in result_segments 

227 ) 

228 return result_segments 

229 

230 @classmethod 

231 def filter_control( 

232 cls, segments: Iterable["Segment"], is_control: bool = False 

233 ) -> Iterable["Segment"]: 

234 """Filter segments by ``is_control`` attribute. 

235 

236 Args: 

237 segments (Iterable[Segment]): An iterable of Segment instances. 

238 is_control (bool, optional): is_control flag to match in search. 

239 

240 Returns: 

241 Iterable[Segment]: And iterable of Segment instances. 

242 

243 """ 

244 if is_control: 

245 return filter(attrgetter("control"), segments) 

246 else: 

247 return filterfalse(attrgetter("control"), segments) 

248 

249 @classmethod 

250 def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]: 

251 """Split a sequence of segments in to a list of lines. 

252 

253 Args: 

254 segments (Iterable[Segment]): Segments potentially containing line feeds. 

255 

256 Yields: 

257 Iterable[List[Segment]]: Iterable of segment lists, one per line. 

258 """ 

259 line: List[Segment] = [] 

260 append = line.append 

261 

262 for segment in segments: 

263 if "\n" in segment.text and not segment.control: 

264 text, style, _ = segment 

265 while text: 

266 _text, new_line, text = text.partition("\n") 

267 if _text: 

268 append(cls(_text, style)) 

269 if new_line: 

270 yield line 

271 line = [] 

272 append = line.append 

273 else: 

274 append(segment) 

275 if line: 

276 yield line 

277 

278 @classmethod 

279 def split_lines_terminator( 

280 cls, segments: Iterable["Segment"] 

281 ) -> Iterable[Tuple[List["Segment"], bool]]: 

282 """Split a sequence of segments in to a list of lines and a boolean to indicate if there was a new line. 

283 

284 Args: 

285 segments (Iterable[Segment]): Segments potentially containing line feeds. 

286 

287 Yields: 

288 Iterable[List[Segment]]: Iterable of segment lists, one per line. 

289 """ 

290 line: List[Segment] = [] 

291 append = line.append 

292 

293 for segment in segments: 

294 if "\n" in segment.text and not segment.control: 

295 text, style, _ = segment 

296 while text: 

297 _text, new_line, text = text.partition("\n") 

298 if _text: 

299 append(cls(_text, style)) 

300 if new_line: 

301 yield (line, True) 

302 line = [] 

303 append = line.append 

304 else: 

305 append(segment) 

306 if line: 

307 yield (line, False) 

308 

309 @classmethod 

310 def split_and_crop_lines( 

311 cls, 

312 segments: Iterable["Segment"], 

313 length: int, 

314 style: Optional[Style] = None, 

315 pad: bool = True, 

316 include_new_lines: bool = True, 

317 ) -> Iterable[List["Segment"]]: 

318 """Split segments in to lines, and crop lines greater than a given length. 

319 

320 Args: 

321 segments (Iterable[Segment]): An iterable of segments, probably 

322 generated from console.render. 

323 length (int): Desired line length. 

324 style (Style, optional): Style to use for any padding. 

325 pad (bool): Enable padding of lines that are less than `length`. 

326 

327 Returns: 

328 Iterable[List[Segment]]: An iterable of lines of segments. 

329 """ 

330 line: List[Segment] = [] 

331 append = line.append 

332 

333 adjust_line_length = cls.adjust_line_length 

334 new_line_segment = cls("\n") 

335 

336 for segment in segments: 

337 if "\n" in segment.text and not segment.control: 

338 text, segment_style, _ = segment 

339 while text: 

340 _text, new_line, text = text.partition("\n") 

341 if _text: 

342 append(cls(_text, segment_style)) 

343 if new_line: 

344 cropped_line = adjust_line_length( 

345 line, length, style=style, pad=pad 

346 ) 

347 if include_new_lines: 

348 cropped_line.append(new_line_segment) 

349 yield cropped_line 

350 line.clear() 

351 else: 

352 append(segment) 

353 if line: 

354 yield adjust_line_length(line, length, style=style, pad=pad) 

355 

356 @classmethod 

357 def adjust_line_length( 

358 cls, 

359 line: List["Segment"], 

360 length: int, 

361 style: Optional[Style] = None, 

362 pad: bool = True, 

363 ) -> List["Segment"]: 

364 """Adjust a line to a given width (cropping or padding as required). 

365 

366 Args: 

367 segments (Iterable[Segment]): A list of segments in a single line. 

368 length (int): The desired width of the line. 

369 style (Style, optional): The style of padding if used (space on the end). Defaults to None. 

370 pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True. 

371 

372 Returns: 

373 List[Segment]: A line of segments with the desired length. 

374 """ 

375 line_length = sum(segment.cell_length for segment in line) 

376 new_line: List[Segment] 

377 

378 if line_length < length: 

379 if pad: 

380 new_line = line + [cls(" " * (length - line_length), style)] 

381 else: 

382 new_line = line[:] 

383 elif line_length > length: 

384 new_line = [] 

385 append = new_line.append 

386 line_length = 0 

387 for segment in line: 

388 segment_length = segment.cell_length 

389 if line_length + segment_length < length or segment.control: 

390 append(segment) 

391 line_length += segment_length 

392 else: 

393 text, segment_style, _ = segment 

394 text = set_cell_size(text, length - line_length) 

395 append(cls(text, segment_style)) 

396 break 

397 else: 

398 new_line = line[:] 

399 return new_line 

400 

401 @classmethod 

402 def get_line_length(cls, line: List["Segment"]) -> int: 

403 """Get the length of list of segments. 

404 

405 Args: 

406 line (List[Segment]): A line encoded as a list of Segments (assumes no '\\\\n' characters), 

407 

408 Returns: 

409 int: The length of the line. 

410 """ 

411 _cell_len = cell_len 

412 return sum(_cell_len(text) for text, style, control in line if not control) 

413 

414 @classmethod 

415 def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]: 

416 """Get the shape (enclosing rectangle) of a list of lines. 

417 

418 Args: 

419 lines (List[List[Segment]]): A list of lines (no '\\\\n' characters). 

420 

421 Returns: 

422 Tuple[int, int]: Width and height in characters. 

423 """ 

424 get_line_length = cls.get_line_length 

425 max_width = max(get_line_length(line) for line in lines) if lines else 0 

426 return (max_width, len(lines)) 

427 

428 @classmethod 

429 def set_shape( 

430 cls, 

431 lines: List[List["Segment"]], 

432 width: int, 

433 height: Optional[int] = None, 

434 style: Optional[Style] = None, 

435 new_lines: bool = False, 

436 ) -> List[List["Segment"]]: 

437 """Set the shape of a list of lines (enclosing rectangle). 

438 

439 Args: 

440 lines (List[List[Segment]]): A list of lines. 

441 width (int): Desired width. 

442 height (int, optional): Desired height or None for no change. 

443 style (Style, optional): Style of any padding added. 

444 new_lines (bool, optional): Padded lines should include "\n". Defaults to False. 

445 

446 Returns: 

447 List[List[Segment]]: New list of lines. 

448 """ 

449 _height = height or len(lines) 

450 

451 blank = ( 

452 [cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)] 

453 ) 

454 

455 adjust_line_length = cls.adjust_line_length 

456 shaped_lines = lines[:_height] 

457 shaped_lines[:] = [ 

458 adjust_line_length(line, width, style=style) for line in lines 

459 ] 

460 if len(shaped_lines) < _height: 

461 shaped_lines.extend([blank] * (_height - len(shaped_lines))) 

462 return shaped_lines 

463 

464 @classmethod 

465 def align_top( 

466 cls: Type["Segment"], 

467 lines: List[List["Segment"]], 

468 width: int, 

469 height: int, 

470 style: Style, 

471 new_lines: bool = False, 

472 ) -> List[List["Segment"]]: 

473 """Aligns lines to top (adds extra lines to bottom as required). 

474 

475 Args: 

476 lines (List[List[Segment]]): A list of lines. 

477 width (int): Desired width. 

478 height (int, optional): Desired height or None for no change. 

479 style (Style): Style of any padding added. 

480 new_lines (bool, optional): Padded lines should include "\n". Defaults to False. 

481 

482 Returns: 

483 List[List[Segment]]: New list of lines. 

484 """ 

485 extra_lines = height - len(lines) 

486 if not extra_lines: 

487 return lines[:] 

488 lines = lines[:height] 

489 blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) 

490 lines = lines + [[blank]] * extra_lines 

491 return lines 

492 

493 @classmethod 

494 def align_bottom( 

495 cls: Type["Segment"], 

496 lines: List[List["Segment"]], 

497 width: int, 

498 height: int, 

499 style: Style, 

500 new_lines: bool = False, 

501 ) -> List[List["Segment"]]: 

502 """Aligns render to bottom (adds extra lines above as required). 

503 

504 Args: 

505 lines (List[List[Segment]]): A list of lines. 

506 width (int): Desired width. 

507 height (int, optional): Desired height or None for no change. 

508 style (Style): Style of any padding added. Defaults to None. 

509 new_lines (bool, optional): Padded lines should include "\n". Defaults to False. 

510 

511 Returns: 

512 List[List[Segment]]: New list of lines. 

513 """ 

514 extra_lines = height - len(lines) 

515 if not extra_lines: 

516 return lines[:] 

517 lines = lines[:height] 

518 blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) 

519 lines = [[blank]] * extra_lines + lines 

520 return lines 

521 

522 @classmethod 

523 def align_middle( 

524 cls: Type["Segment"], 

525 lines: List[List["Segment"]], 

526 width: int, 

527 height: int, 

528 style: Style, 

529 new_lines: bool = False, 

530 ) -> List[List["Segment"]]: 

531 """Aligns lines to middle (adds extra lines to above and below as required). 

532 

533 Args: 

534 lines (List[List[Segment]]): A list of lines. 

535 width (int): Desired width. 

536 height (int, optional): Desired height or None for no change. 

537 style (Style): Style of any padding added. 

538 new_lines (bool, optional): Padded lines should include "\n". Defaults to False. 

539 

540 Returns: 

541 List[List[Segment]]: New list of lines. 

542 """ 

543 extra_lines = height - len(lines) 

544 if not extra_lines: 

545 return lines[:] 

546 lines = lines[:height] 

547 blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) 

548 top_lines = extra_lines // 2 

549 bottom_lines = extra_lines - top_lines 

550 lines = [[blank]] * top_lines + lines + [[blank]] * bottom_lines 

551 return lines 

552 

553 @classmethod 

554 def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: 

555 """Simplify an iterable of segments by combining contiguous segments with the same style. 

556 

557 Args: 

558 segments (Iterable[Segment]): An iterable of segments. 

559 

560 Returns: 

561 Iterable[Segment]: A possibly smaller iterable of segments that will render the same way. 

562 """ 

563 iter_segments = iter(segments) 

564 try: 

565 last_segment = next(iter_segments) 

566 except StopIteration: 

567 return 

568 

569 _Segment = Segment 

570 for segment in iter_segments: 

571 if last_segment.style == segment.style and not segment.control: 

572 last_segment = _Segment( 

573 last_segment.text + segment.text, last_segment.style 

574 ) 

575 else: 

576 yield last_segment 

577 last_segment = segment 

578 yield last_segment 

579 

580 @classmethod 

581 def strip_links(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: 

582 """Remove all links from an iterable of styles. 

583 

584 Args: 

585 segments (Iterable[Segment]): An iterable segments. 

586 

587 Yields: 

588 Segment: Segments with link removed. 

589 """ 

590 for segment in segments: 

591 if segment.control or segment.style is None: 

592 yield segment 

593 else: 

594 text, style, _control = segment 

595 yield cls(text, style.update_link(None) if style else None) 

596 

597 @classmethod 

598 def strip_styles(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: 

599 """Remove all styles from an iterable of segments. 

600 

601 Args: 

602 segments (Iterable[Segment]): An iterable segments. 

603 

604 Yields: 

605 Segment: Segments with styles replace with None 

606 """ 

607 for text, _style, control in segments: 

608 yield cls(text, None, control) 

609 

610 @classmethod 

611 def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: 

612 """Remove all color from an iterable of segments. 

613 

614 Args: 

615 segments (Iterable[Segment]): An iterable segments. 

616 

617 Yields: 

618 Segment: Segments with colorless style. 

619 """ 

620 

621 cache: Dict[Style, Style] = {} 

622 for text, style, control in segments: 

623 if style: 

624 colorless_style = cache.get(style) 

625 if colorless_style is None: 

626 colorless_style = style.without_color 

627 cache[style] = colorless_style 

628 yield cls(text, colorless_style, control) 

629 else: 

630 yield cls(text, None, control) 

631 

632 @classmethod 

633 def divide( 

634 cls, segments: Iterable["Segment"], cuts: Iterable[int] 

635 ) -> Iterable[List["Segment"]]: 

636 """Divides an iterable of segments in to portions. 

637 

638 Args: 

639 cuts (Iterable[int]): Cell positions where to divide. 

640 

641 Yields: 

642 [Iterable[List[Segment]]]: An iterable of Segments in List. 

643 """ 

644 split_segments: List["Segment"] = [] 

645 add_segment = split_segments.append 

646 

647 iter_cuts = iter(cuts) 

648 

649 while True: 

650 cut = next(iter_cuts, -1) 

651 if cut == -1: 

652 return 

653 if cut != 0: 

654 break 

655 yield [] 

656 pos = 0 

657 

658 segments_clear = split_segments.clear 

659 segments_copy = split_segments.copy 

660 

661 _cell_len = cached_cell_len 

662 for segment in segments: 

663 text, _style, control = segment 

664 while text: 

665 end_pos = pos if control else pos + _cell_len(text) 

666 if end_pos < cut: 

667 add_segment(segment) 

668 pos = end_pos 

669 break 

670 

671 if end_pos == cut: 

672 add_segment(segment) 

673 yield segments_copy() 

674 segments_clear() 

675 pos = end_pos 

676 

677 cut = next(iter_cuts, -1) 

678 if cut == -1: 

679 if split_segments: 

680 yield segments_copy() 

681 return 

682 

683 break 

684 

685 else: 

686 before, segment = segment.split_cells(cut - pos) 

687 text, _style, control = segment 

688 add_segment(before) 

689 yield segments_copy() 

690 segments_clear() 

691 pos = cut 

692 

693 cut = next(iter_cuts, -1) 

694 if cut == -1: 

695 if split_segments: 

696 yield segments_copy() 

697 return 

698 

699 yield segments_copy() 

700 

701 

702class Segments: 

703 """A simple renderable to render an iterable of segments. This class may be useful if 

704 you want to print segments outside of a __rich_console__ method. 

705 

706 Args: 

707 segments (Iterable[Segment]): An iterable of segments. 

708 new_lines (bool, optional): Add new lines between segments. Defaults to False. 

709 """ 

710 

711 def __init__(self, segments: Iterable[Segment], new_lines: bool = False) -> None: 

712 self.segments = list(segments) 

713 self.new_lines = new_lines 

714 

715 def __rich_console__( 

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

717 ) -> "RenderResult": 

718 if self.new_lines: 

719 line = Segment.line() 

720 for segment in self.segments: 

721 yield segment 

722 yield line 

723 else: 

724 yield from self.segments 

725 

726 

727class SegmentLines: 

728 def __init__(self, lines: Iterable[List[Segment]], new_lines: bool = False) -> None: 

729 """A simple renderable containing a number of lines of segments. May be used as an intermediate 

730 in rendering process. 

731 

732 Args: 

733 lines (Iterable[List[Segment]]): Lists of segments forming lines. 

734 new_lines (bool, optional): Insert new lines after each line. Defaults to False. 

735 """ 

736 self.lines = list(lines) 

737 self.new_lines = new_lines 

738 

739 def __rich_console__( 

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

741 ) -> "RenderResult": 

742 if self.new_lines: 

743 new_line = Segment.line() 

744 for line in self.lines: 

745 yield from line 

746 yield new_line 

747 else: 

748 for line in self.lines: 

749 yield from line 

750 

751 

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

753 from rich.console import Console 

754 from rich.syntax import Syntax 

755 from rich.text import Text 

756 

757 code = """from rich.console import Console 

758console = Console() 

759text = Text.from_markup("Hello, [bold magenta]World[/]!") 

760console.print(text)""" 

761 

762 text = Text.from_markup("Hello, [bold magenta]World[/]!") 

763 

764 console = Console() 

765 

766 console.rule("rich.Segment") 

767 console.print( 

768 "A Segment is the last step in the Rich render process before generating text with ANSI codes." 

769 ) 

770 console.print("\nConsider the following code:\n") 

771 console.print(Syntax(code, "python", line_numbers=True)) 

772 console.print() 

773 console.print( 

774 "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the following:\n" 

775 ) 

776 fragments = list(console.render(text)) 

777 console.print(fragments) 

778 console.print() 

779 console.print("The Segments are then processed to produce the following output:\n") 

780 console.print(text) 

781 console.print( 

782 "\nYou will only need to know this if you are implementing your own Rich renderables." 

783 )