Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/markdown.py: 94%

370 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-18 06:13 +0000

1from __future__ import annotations 

2 

3import sys 

4from typing import ClassVar, Dict, Iterable, List, Optional, Type, Union 

5 

6from markdown_it import MarkdownIt 

7from markdown_it.token import Token 

8 

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

10 from typing import get_args 

11else: 

12 from typing_extensions import get_args # pragma: no cover 

13 

14from rich.table import Table 

15 

16from . import box 

17from ._loop import loop_first 

18from ._stack import Stack 

19from .console import Console, ConsoleOptions, JustifyMethod, RenderResult 

20from .containers import Renderables 

21from .jupyter import JupyterMixin 

22from .panel import Panel 

23from .rule import Rule 

24from .segment import Segment 

25from .style import Style, StyleStack 

26from .syntax import Syntax 

27from .text import Text, TextType 

28 

29 

30class MarkdownElement: 

31 new_line: ClassVar[bool] = True 

32 

33 @classmethod 

34 def create(cls, markdown: "Markdown", token: Token) -> "MarkdownElement": 

35 """Factory to create markdown element, 

36 

37 Args: 

38 markdown (Markdown): The parent Markdown object. 

39 token (Token): A node from markdown-it. 

40 

41 Returns: 

42 MarkdownElement: A new markdown element 

43 """ 

44 return cls() 

45 

46 def on_enter(self, context: "MarkdownContext") -> None: 

47 """Called when the node is entered. 

48 

49 Args: 

50 context (MarkdownContext): The markdown context. 

51 """ 

52 

53 def on_text(self, context: "MarkdownContext", text: TextType) -> None: 

54 """Called when text is parsed. 

55 

56 Args: 

57 context (MarkdownContext): The markdown context. 

58 """ 

59 

60 def on_leave(self, context: "MarkdownContext") -> None: 

61 """Called when the parser leaves the element. 

62 

63 Args: 

64 context (MarkdownContext): [description] 

65 """ 

66 

67 def on_child_close( 

68 self, context: "MarkdownContext", child: "MarkdownElement" 

69 ) -> bool: 

70 """Called when a child element is closed. 

71 

72 This method allows a parent element to take over rendering of its children. 

73 

74 Args: 

75 context (MarkdownContext): The markdown context. 

76 child (MarkdownElement): The child markdown element. 

77 

78 Returns: 

79 bool: Return True to render the element, or False to not render the element. 

80 """ 

81 return True 

82 

83 def __rich_console__( 

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

85 ) -> "RenderResult": 

86 return () 

87 

88 

89class UnknownElement(MarkdownElement): 

90 """An unknown element. 

91 

92 Hopefully there will be no unknown elements, and we will have a MarkdownElement for 

93 everything in the document. 

94 

95 """ 

96 

97 

98class TextElement(MarkdownElement): 

99 """Base class for elements that render text.""" 

100 

101 style_name = "none" 

102 

103 def on_enter(self, context: "MarkdownContext") -> None: 

104 self.style = context.enter_style(self.style_name) 

105 self.text = Text(justify="left") 

106 

107 def on_text(self, context: "MarkdownContext", text: TextType) -> None: 

108 self.text.append(text, context.current_style if isinstance(text, str) else None) 

109 

110 def on_leave(self, context: "MarkdownContext") -> None: 

111 context.leave_style() 

112 

113 

114class Paragraph(TextElement): 

115 """A Paragraph.""" 

116 

117 style_name = "markdown.paragraph" 

118 justify: JustifyMethod 

119 

120 @classmethod 

121 def create(cls, markdown: "Markdown", token: Token) -> "Paragraph": 

122 return cls(justify=markdown.justify or "left") 

123 

124 def __init__(self, justify: JustifyMethod) -> None: 

125 self.justify = justify 

126 

127 def __rich_console__( 

128 self, console: Console, options: ConsoleOptions 

129 ) -> RenderResult: 

130 self.text.justify = self.justify 

131 yield self.text 

132 

133 

134class Heading(TextElement): 

135 """A heading.""" 

136 

137 @classmethod 

138 def create(cls, markdown: "Markdown", token: Token) -> "Heading": 

139 return cls(token.tag) 

140 

141 def on_enter(self, context: "MarkdownContext") -> None: 

142 self.text = Text() 

143 context.enter_style(self.style_name) 

144 

145 def __init__(self, tag: str) -> None: 

146 self.tag = tag 

147 self.style_name = f"markdown.{tag}" 

148 super().__init__() 

149 

150 def __rich_console__( 

151 self, console: Console, options: ConsoleOptions 

152 ) -> RenderResult: 

153 text = self.text 

154 text.justify = "center" 

155 if self.tag == "h1": 

156 # Draw a border around h1s 

157 yield Panel( 

158 text, 

159 box=box.HEAVY, 

160 style="markdown.h1.border", 

161 ) 

162 else: 

163 # Styled text for h2 and beyond 

164 if self.tag == "h2": 

165 yield Text("") 

166 yield text 

167 

168 

169class CodeBlock(TextElement): 

170 """A code block with syntax highlighting.""" 

171 

172 style_name = "markdown.code_block" 

173 

174 @classmethod 

175 def create(cls, markdown: "Markdown", token: Token) -> "CodeBlock": 

176 node_info = token.info or "" 

177 lexer_name = node_info.partition(" ")[0] 

178 return cls(lexer_name or "text", markdown.code_theme) 

179 

180 def __init__(self, lexer_name: str, theme: str) -> None: 

181 self.lexer_name = lexer_name 

182 self.theme = theme 

183 

184 def __rich_console__( 

185 self, console: Console, options: ConsoleOptions 

186 ) -> RenderResult: 

187 code = str(self.text).rstrip() 

188 syntax = Syntax( 

189 code, self.lexer_name, theme=self.theme, word_wrap=True, padding=1 

190 ) 

191 yield syntax 

192 

193 

194class BlockQuote(TextElement): 

195 """A block quote.""" 

196 

197 style_name = "markdown.block_quote" 

198 

199 def __init__(self) -> None: 

200 self.elements: Renderables = Renderables() 

201 

202 def on_child_close( 

203 self, context: "MarkdownContext", child: "MarkdownElement" 

204 ) -> bool: 

205 self.elements.append(child) 

206 return False 

207 

208 def __rich_console__( 

209 self, console: Console, options: ConsoleOptions 

210 ) -> RenderResult: 

211 render_options = options.update(width=options.max_width - 4) 

212 lines = console.render_lines(self.elements, render_options, style=self.style) 

213 style = self.style 

214 new_line = Segment("\n") 

215 padding = Segment("▌ ", style) 

216 for line in lines: 

217 yield padding 

218 yield from line 

219 yield new_line 

220 

221 

222class HorizontalRule(MarkdownElement): 

223 """A horizontal rule to divide sections.""" 

224 

225 new_line = False 

226 

227 def __rich_console__( 

228 self, console: Console, options: ConsoleOptions 

229 ) -> RenderResult: 

230 style = console.get_style("markdown.hr", default="none") 

231 yield Rule(style=style) 

232 

233 

234class TableElement(MarkdownElement): 

235 """MarkdownElement corresponding to `table_open`.""" 

236 

237 def __init__(self) -> None: 

238 self.header: TableHeaderElement | None = None 

239 self.body: TableBodyElement | None = None 

240 

241 def on_child_close( 

242 self, context: "MarkdownContext", child: "MarkdownElement" 

243 ) -> bool: 

244 if isinstance(child, TableHeaderElement): 

245 self.header = child 

246 elif isinstance(child, TableBodyElement): 

247 self.body = child 

248 else: 

249 raise RuntimeError("Couldn't process markdown table.") 

250 return False 

251 

252 def __rich_console__( 

253 self, console: Console, options: ConsoleOptions 

254 ) -> RenderResult: 

255 table = Table(box=box.SIMPLE_HEAVY) 

256 

257 if self.header is not None and self.header.row is not None: 

258 for column in self.header.row.cells: 

259 table.add_column(column.content) 

260 

261 if self.body is not None: 

262 for row in self.body.rows: 

263 row_content = [element.content for element in row.cells] 

264 table.add_row(*row_content) 

265 

266 yield table 

267 

268 

269class TableHeaderElement(MarkdownElement): 

270 """MarkdownElement corresponding to `thead_open` and `thead_close`.""" 

271 

272 def __init__(self) -> None: 

273 self.row: TableRowElement | None = None 

274 

275 def on_child_close( 

276 self, context: "MarkdownContext", child: "MarkdownElement" 

277 ) -> bool: 

278 assert isinstance(child, TableRowElement) 

279 self.row = child 

280 return False 

281 

282 

283class TableBodyElement(MarkdownElement): 

284 """MarkdownElement corresponding to `tbody_open` and `tbody_close`.""" 

285 

286 def __init__(self) -> None: 

287 self.rows: list[TableRowElement] = [] 

288 

289 def on_child_close( 

290 self, context: "MarkdownContext", child: "MarkdownElement" 

291 ) -> bool: 

292 assert isinstance(child, TableRowElement) 

293 self.rows.append(child) 

294 return False 

295 

296 

297class TableRowElement(MarkdownElement): 

298 """MarkdownElement corresponding to `tr_open` and `tr_close`.""" 

299 

300 def __init__(self) -> None: 

301 self.cells: List[TableDataElement] = [] 

302 

303 def on_child_close( 

304 self, context: "MarkdownContext", child: "MarkdownElement" 

305 ) -> bool: 

306 assert isinstance(child, TableDataElement) 

307 self.cells.append(child) 

308 return False 

309 

310 

311class TableDataElement(MarkdownElement): 

312 """MarkdownElement corresponding to `td_open` and `td_close` 

313 and `th_open` and `th_close`.""" 

314 

315 @classmethod 

316 def create(cls, markdown: "Markdown", token: Token) -> "MarkdownElement": 

317 style = str(token.attrs.get("style")) or "" 

318 

319 justify: JustifyMethod 

320 if "text-align:right" in style: 

321 justify = "right" 

322 elif "text-align:center" in style: 

323 justify = "center" 

324 elif "text-align:left" in style: 

325 justify = "left" 

326 else: 

327 justify = "default" 

328 

329 assert justify in get_args(JustifyMethod) 

330 return cls(justify=justify) 

331 

332 def __init__(self, justify: JustifyMethod) -> None: 

333 self.content: Text = Text("", justify=justify) 

334 self.justify = justify 

335 

336 def on_text(self, context: "MarkdownContext", text: TextType) -> None: 

337 text = Text(text) if isinstance(text, str) else text 

338 text.stylize(context.current_style) 

339 self.content.append_text(text) 

340 

341 

342class ListElement(MarkdownElement): 

343 """A list element.""" 

344 

345 @classmethod 

346 def create(cls, markdown: "Markdown", token: Token) -> "ListElement": 

347 return cls(token.type, int(token.attrs.get("start", 1))) 

348 

349 def __init__(self, list_type: str, list_start: int | None) -> None: 

350 self.items: List[ListItem] = [] 

351 self.list_type = list_type 

352 self.list_start = list_start 

353 

354 def on_child_close( 

355 self, context: "MarkdownContext", child: "MarkdownElement" 

356 ) -> bool: 

357 assert isinstance(child, ListItem) 

358 self.items.append(child) 

359 return False 

360 

361 def __rich_console__( 

362 self, console: Console, options: ConsoleOptions 

363 ) -> RenderResult: 

364 if self.list_type == "bullet_list_open": 

365 for item in self.items: 

366 yield from item.render_bullet(console, options) 

367 else: 

368 number = 1 if self.list_start is None else self.list_start 

369 last_number = number + len(self.items) 

370 for index, item in enumerate(self.items): 

371 yield from item.render_number( 

372 console, options, number + index, last_number 

373 ) 

374 

375 

376class ListItem(TextElement): 

377 """An item in a list.""" 

378 

379 style_name = "markdown.item" 

380 

381 def __init__(self) -> None: 

382 self.elements: Renderables = Renderables() 

383 

384 def on_child_close( 

385 self, context: "MarkdownContext", child: "MarkdownElement" 

386 ) -> bool: 

387 self.elements.append(child) 

388 return False 

389 

390 def render_bullet(self, console: Console, options: ConsoleOptions) -> RenderResult: 

391 render_options = options.update(width=options.max_width - 3) 

392 lines = console.render_lines(self.elements, render_options, style=self.style) 

393 bullet_style = console.get_style("markdown.item.bullet", default="none") 

394 

395 bullet = Segment(" • ", bullet_style) 

396 padding = Segment(" " * 3, bullet_style) 

397 new_line = Segment("\n") 

398 for first, line in loop_first(lines): 

399 yield bullet if first else padding 

400 yield from line 

401 yield new_line 

402 

403 def render_number( 

404 self, console: Console, options: ConsoleOptions, number: int, last_number: int 

405 ) -> RenderResult: 

406 number_width = len(str(last_number)) + 2 

407 render_options = options.update(width=options.max_width - number_width) 

408 lines = console.render_lines(self.elements, render_options, style=self.style) 

409 number_style = console.get_style("markdown.item.number", default="none") 

410 

411 new_line = Segment("\n") 

412 padding = Segment(" " * number_width, number_style) 

413 numeral = Segment(f"{number}".rjust(number_width - 1) + " ", number_style) 

414 for first, line in loop_first(lines): 

415 yield numeral if first else padding 

416 yield from line 

417 yield new_line 

418 

419 

420class Link(TextElement): 

421 @classmethod 

422 def create(cls, markdown: "Markdown", token: Token) -> "MarkdownElement": 

423 url = token.attrs.get("href", "#") 

424 return cls(token.content, str(url)) 

425 

426 def __init__(self, text: str, href: str): 

427 self.text = Text(text) 

428 self.href = href 

429 

430 

431class ImageItem(TextElement): 

432 """Renders a placeholder for an image.""" 

433 

434 new_line = False 

435 

436 @classmethod 

437 def create(cls, markdown: "Markdown", token: Token) -> "MarkdownElement": 

438 """Factory to create markdown element, 

439 

440 Args: 

441 markdown (Markdown): The parent Markdown object. 

442 token (Any): A token from markdown-it. 

443 

444 Returns: 

445 MarkdownElement: A new markdown element 

446 """ 

447 return cls(str(token.attrs.get("src", "")), markdown.hyperlinks) 

448 

449 def __init__(self, destination: str, hyperlinks: bool) -> None: 

450 self.destination = destination 

451 self.hyperlinks = hyperlinks 

452 self.link: Optional[str] = None 

453 super().__init__() 

454 

455 def on_enter(self, context: "MarkdownContext") -> None: 

456 self.link = context.current_style.link 

457 self.text = Text(justify="left") 

458 super().on_enter(context) 

459 

460 def __rich_console__( 

461 self, console: Console, options: ConsoleOptions 

462 ) -> RenderResult: 

463 link_style = Style(link=self.link or self.destination or None) 

464 title = self.text or Text(self.destination.strip("/").rsplit("/", 1)[-1]) 

465 if self.hyperlinks: 

466 title.stylize(link_style) 

467 text = Text.assemble("🌆 ", title, " ", end="") 

468 yield text 

469 

470 

471class MarkdownContext: 

472 """Manages the console render state.""" 

473 

474 def __init__( 

475 self, 

476 console: Console, 

477 options: ConsoleOptions, 

478 style: Style, 

479 inline_code_lexer: Optional[str] = None, 

480 inline_code_theme: str = "monokai", 

481 ) -> None: 

482 self.console = console 

483 self.options = options 

484 self.style_stack: StyleStack = StyleStack(style) 

485 self.stack: Stack[MarkdownElement] = Stack() 

486 

487 self._syntax: Optional[Syntax] = None 

488 if inline_code_lexer is not None: 

489 self._syntax = Syntax("", inline_code_lexer, theme=inline_code_theme) 

490 

491 @property 

492 def current_style(self) -> Style: 

493 """Current style which is the product of all styles on the stack.""" 

494 return self.style_stack.current 

495 

496 def on_text(self, text: str, node_type: str) -> None: 

497 """Called when the parser visits text.""" 

498 if node_type in {"fence", "code_inline"} and self._syntax is not None: 

499 highlight_text = self._syntax.highlight(text) 

500 highlight_text.rstrip() 

501 self.stack.top.on_text( 

502 self, Text.assemble(highlight_text, style=self.style_stack.current) 

503 ) 

504 else: 

505 self.stack.top.on_text(self, text) 

506 

507 def enter_style(self, style_name: Union[str, Style]) -> Style: 

508 """Enter a style context.""" 

509 style = self.console.get_style(style_name, default="none") 

510 self.style_stack.push(style) 

511 return self.current_style 

512 

513 def leave_style(self) -> Style: 

514 """Leave a style context.""" 

515 style = self.style_stack.pop() 

516 return style 

517 

518 

519class Markdown(JupyterMixin): 

520 """A Markdown renderable. 

521 

522 Args: 

523 markup (str): A string containing markdown. 

524 code_theme (str, optional): Pygments theme for code blocks. Defaults to "monokai". 

525 justify (JustifyMethod, optional): Justify value for paragraphs. Defaults to None. 

526 style (Union[str, Style], optional): Optional style to apply to markdown. 

527 hyperlinks (bool, optional): Enable hyperlinks. Defaults to ``True``. 

528 inline_code_lexer: (str, optional): Lexer to use if inline code highlighting is 

529 enabled. Defaults to None. 

530 inline_code_theme: (Optional[str], optional): Pygments theme for inline code 

531 highlighting, or None for no highlighting. Defaults to None. 

532 """ 

533 

534 elements: ClassVar[Dict[str, Type[MarkdownElement]]] = { 

535 "paragraph_open": Paragraph, 

536 "heading_open": Heading, 

537 "fence": CodeBlock, 

538 "code_block": CodeBlock, 

539 "blockquote_open": BlockQuote, 

540 "hr": HorizontalRule, 

541 "bullet_list_open": ListElement, 

542 "ordered_list_open": ListElement, 

543 "list_item_open": ListItem, 

544 "image": ImageItem, 

545 "table_open": TableElement, 

546 "tbody_open": TableBodyElement, 

547 "thead_open": TableHeaderElement, 

548 "tr_open": TableRowElement, 

549 "td_open": TableDataElement, 

550 "th_open": TableDataElement, 

551 } 

552 

553 inlines = {"em", "strong", "code", "s"} 

554 

555 def __init__( 

556 self, 

557 markup: str, 

558 code_theme: str = "monokai", 

559 justify: Optional[JustifyMethod] = None, 

560 style: Union[str, Style] = "none", 

561 hyperlinks: bool = True, 

562 inline_code_lexer: Optional[str] = None, 

563 inline_code_theme: Optional[str] = None, 

564 ) -> None: 

565 parser = MarkdownIt().enable("strikethrough").enable("table") 

566 self.markup = markup 

567 self.parsed = parser.parse(markup) 

568 self.code_theme = code_theme 

569 self.justify: Optional[JustifyMethod] = justify 

570 self.style = style 

571 self.hyperlinks = hyperlinks 

572 self.inline_code_lexer = inline_code_lexer 

573 self.inline_code_theme = inline_code_theme or code_theme 

574 

575 def _flatten_tokens(self, tokens: Iterable[Token]) -> Iterable[Token]: 

576 """Flattens the token stream.""" 

577 for token in tokens: 

578 is_fence = token.type == "fence" 

579 is_image = token.tag == "img" 

580 if token.children and not (is_image or is_fence): 

581 yield from self._flatten_tokens(token.children) 

582 else: 

583 yield token 

584 

585 def __rich_console__( 

586 self, console: Console, options: ConsoleOptions 

587 ) -> RenderResult: 

588 """Render markdown to the console.""" 

589 style = console.get_style(self.style, default="none") 

590 options = options.update(height=None) 

591 context = MarkdownContext( 

592 console, 

593 options, 

594 style, 

595 inline_code_lexer=self.inline_code_lexer, 

596 inline_code_theme=self.inline_code_theme, 

597 ) 

598 tokens = self.parsed 

599 inline_style_tags = self.inlines 

600 new_line = False 

601 _new_line_segment = Segment.line() 

602 

603 for token in self._flatten_tokens(tokens): 

604 node_type = token.type 

605 tag = token.tag 

606 

607 entering = token.nesting == 1 

608 exiting = token.nesting == -1 

609 self_closing = token.nesting == 0 

610 

611 if node_type == "text": 

612 context.on_text(token.content, node_type) 

613 elif node_type == "hardbreak": 

614 context.on_text("\n", node_type) 

615 elif node_type == "softbreak": 

616 context.on_text(" ", node_type) 

617 elif node_type == "link_open": 

618 href = str(token.attrs.get("href", "")) 

619 if self.hyperlinks: 

620 link_style = console.get_style("markdown.link_url", default="none") 

621 link_style += Style(link=href) 

622 context.enter_style(link_style) 

623 else: 

624 context.stack.push(Link.create(self, token)) 

625 elif node_type == "link_close": 

626 if self.hyperlinks: 

627 context.leave_style() 

628 else: 

629 element = context.stack.pop() 

630 assert isinstance(element, Link) 

631 link_style = console.get_style("markdown.link", default="none") 

632 context.enter_style(link_style) 

633 context.on_text(element.text.plain, node_type) 

634 context.leave_style() 

635 context.on_text(" (", node_type) 

636 link_url_style = console.get_style( 

637 "markdown.link_url", default="none" 

638 ) 

639 context.enter_style(link_url_style) 

640 context.on_text(element.href, node_type) 

641 context.leave_style() 

642 context.on_text(")", node_type) 

643 elif ( 

644 tag in inline_style_tags 

645 and node_type != "fence" 

646 and node_type != "code_block" 

647 ): 

648 if entering: 

649 # If it's an opening inline token e.g. strong, em, etc. 

650 # Then we move into a style context i.e. push to stack. 

651 context.enter_style(f"markdown.{tag}") 

652 elif exiting: 

653 # If it's a closing inline style, then we pop the style 

654 # off of the stack, to move out of the context of it... 

655 context.leave_style() 

656 else: 

657 # If it's a self-closing inline style e.g. `code_inline` 

658 context.enter_style(f"markdown.{tag}") 

659 if token.content: 

660 context.on_text(token.content, node_type) 

661 context.leave_style() 

662 else: 

663 # Map the markdown tag -> MarkdownElement renderable 

664 element_class = self.elements.get(token.type) or UnknownElement 

665 element = element_class.create(self, token) 

666 

667 if entering or self_closing: 

668 context.stack.push(element) 

669 element.on_enter(context) 

670 

671 if exiting: # CLOSING tag 

672 element = context.stack.pop() 

673 

674 should_render = not context.stack or ( 

675 context.stack 

676 and context.stack.top.on_child_close(context, element) 

677 ) 

678 

679 if should_render: 

680 if new_line: 

681 yield _new_line_segment 

682 

683 yield from console.render(element, context.options) 

684 elif self_closing: # SELF-CLOSING tags (e.g. text, code, image) 

685 context.stack.pop() 

686 text = token.content 

687 if text is not None: 

688 element.on_text(context, text) 

689 

690 should_render = ( 

691 not context.stack 

692 or context.stack 

693 and context.stack.top.on_child_close(context, element) 

694 ) 

695 if should_render: 

696 if new_line: 

697 yield _new_line_segment 

698 yield from console.render(element, context.options) 

699 

700 if exiting or self_closing: 

701 element.on_leave(context) 

702 new_line = element.new_line 

703 

704 

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

706 import argparse 

707 import sys 

708 

709 parser = argparse.ArgumentParser( 

710 description="Render Markdown to the console with Rich" 

711 ) 

712 parser.add_argument( 

713 "path", 

714 metavar="PATH", 

715 help="path to markdown file, or - for stdin", 

716 ) 

717 parser.add_argument( 

718 "-c", 

719 "--force-color", 

720 dest="force_color", 

721 action="store_true", 

722 default=None, 

723 help="force color for non-terminals", 

724 ) 

725 parser.add_argument( 

726 "-t", 

727 "--code-theme", 

728 dest="code_theme", 

729 default="monokai", 

730 help="pygments code theme", 

731 ) 

732 parser.add_argument( 

733 "-i", 

734 "--inline-code-lexer", 

735 dest="inline_code_lexer", 

736 default=None, 

737 help="inline_code_lexer", 

738 ) 

739 parser.add_argument( 

740 "-y", 

741 "--hyperlinks", 

742 dest="hyperlinks", 

743 action="store_true", 

744 help="enable hyperlinks", 

745 ) 

746 parser.add_argument( 

747 "-w", 

748 "--width", 

749 type=int, 

750 dest="width", 

751 default=None, 

752 help="width of output (default will auto-detect)", 

753 ) 

754 parser.add_argument( 

755 "-j", 

756 "--justify", 

757 dest="justify", 

758 action="store_true", 

759 help="enable full text justify", 

760 ) 

761 parser.add_argument( 

762 "-p", 

763 "--page", 

764 dest="page", 

765 action="store_true", 

766 help="use pager to scroll output", 

767 ) 

768 args = parser.parse_args() 

769 

770 from rich.console import Console 

771 

772 if args.path == "-": 

773 markdown_body = sys.stdin.read() 

774 else: 

775 with open(args.path, "rt", encoding="utf-8") as markdown_file: 

776 markdown_body = markdown_file.read() 

777 

778 markdown = Markdown( 

779 markdown_body, 

780 justify="full" if args.justify else "left", 

781 code_theme=args.code_theme, 

782 hyperlinks=args.hyperlinks, 

783 inline_code_lexer=args.inline_code_lexer, 

784 ) 

785 if args.page: 

786 import io 

787 import pydoc 

788 

789 fileio = io.StringIO() 

790 console = Console( 

791 file=fileio, force_terminal=args.force_color, width=args.width 

792 ) 

793 console.print(markdown) 

794 pydoc.pager(fileio.getvalue()) 

795 

796 else: 

797 console = Console( 

798 force_terminal=args.force_color, width=args.width, record=True 

799 ) 

800 console.print(markdown)