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

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

368 statements  

1from __future__ import annotations 

2 

3import sys 

4from typing import ClassVar, Iterable, get_args 

5 

6from markdown_it import MarkdownIt 

7from markdown_it.token import Token 

8 

9from rich.table import Table 

10 

11from . import box 

12from ._loop import loop_first 

13from ._stack import Stack 

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

15from .containers import Renderables 

16from .jupyter import JupyterMixin 

17from .panel import Panel 

18from .rule import Rule 

19from .segment import Segment 

20from .style import Style, StyleStack 

21from .syntax import Syntax 

22from .text import Text, TextType 

23 

24 

25class MarkdownElement: 

26 new_line: ClassVar[bool] = True 

27 

28 @classmethod 

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

30 """Factory to create markdown element, 

31 

32 Args: 

33 markdown (Markdown): The parent Markdown object. 

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

35 

36 Returns: 

37 MarkdownElement: A new markdown element 

38 """ 

39 return cls() 

40 

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

42 """Called when the node is entered. 

43 

44 Args: 

45 context (MarkdownContext): The markdown context. 

46 """ 

47 

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

49 """Called when text is parsed. 

50 

51 Args: 

52 context (MarkdownContext): The markdown context. 

53 """ 

54 

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

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

57 

58 Args: 

59 context (MarkdownContext): [description] 

60 """ 

61 

62 def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: 

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

64 

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

66 

67 Args: 

68 context (MarkdownContext): The markdown context. 

69 child (MarkdownElement): The child markdown element. 

70 

71 Returns: 

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

73 """ 

74 return True 

75 

76 def __rich_console__( 

77 self, console: Console, options: ConsoleOptions 

78 ) -> RenderResult: 

79 return () 

80 

81 

82class UnknownElement(MarkdownElement): 

83 """An unknown element. 

84 

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

86 everything in the document. 

87 

88 """ 

89 

90 

91class TextElement(MarkdownElement): 

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

93 

94 style_name = "none" 

95 

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

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

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

99 

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

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

102 

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

104 context.leave_style() 

105 

106 

107class Paragraph(TextElement): 

108 """A Paragraph.""" 

109 

110 style_name = "markdown.paragraph" 

111 justify: JustifyMethod 

112 

113 @classmethod 

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

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

116 

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

118 self.justify = justify 

119 

120 def __rich_console__( 

121 self, console: Console, options: ConsoleOptions 

122 ) -> RenderResult: 

123 self.text.justify = self.justify 

124 yield self.text 

125 

126 

127class Heading(TextElement): 

128 """A heading.""" 

129 

130 @classmethod 

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

132 return cls(token.tag) 

133 

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

135 self.text = Text() 

136 context.enter_style(self.style_name) 

137 

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

139 self.tag = tag 

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

141 super().__init__() 

142 

143 def __rich_console__( 

144 self, console: Console, options: ConsoleOptions 

145 ) -> RenderResult: 

146 text = self.text 

147 text.justify = "center" 

148 if self.tag == "h1": 

149 # Draw a border around h1s 

150 yield Panel( 

151 text, 

152 box=box.HEAVY, 

153 style="markdown.h1.border", 

154 ) 

155 else: 

156 # Styled text for h2 and beyond 

157 if self.tag == "h2": 

158 yield Text("") 

159 yield text 

160 

161 

162class CodeBlock(TextElement): 

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

164 

165 style_name = "markdown.code_block" 

166 

167 @classmethod 

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

169 node_info = token.info or "" 

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

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

172 

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

174 self.lexer_name = lexer_name 

175 self.theme = theme 

176 

177 def __rich_console__( 

178 self, console: Console, options: ConsoleOptions 

179 ) -> RenderResult: 

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

181 syntax = Syntax( 

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

183 ) 

184 yield syntax 

185 

186 

187class BlockQuote(TextElement): 

188 """A block quote.""" 

189 

190 style_name = "markdown.block_quote" 

191 

192 def __init__(self) -> None: 

193 self.elements: Renderables = Renderables() 

194 

195 def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: 

196 self.elements.append(child) 

197 return False 

198 

199 def __rich_console__( 

200 self, console: Console, options: ConsoleOptions 

201 ) -> RenderResult: 

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

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

204 style = self.style 

205 new_line = Segment("\n") 

206 padding = Segment("▌ ", style) 

207 for line in lines: 

208 yield padding 

209 yield from line 

210 yield new_line 

211 

212 

213class HorizontalRule(MarkdownElement): 

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

215 

216 new_line = False 

217 

218 def __rich_console__( 

219 self, console: Console, options: ConsoleOptions 

220 ) -> RenderResult: 

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

222 yield Rule(style=style) 

223 

224 

225class TableElement(MarkdownElement): 

226 """MarkdownElement corresponding to `table_open`.""" 

227 

228 def __init__(self) -> None: 

229 self.header: TableHeaderElement | None = None 

230 self.body: TableBodyElement | None = None 

231 

232 def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: 

233 if isinstance(child, TableHeaderElement): 

234 self.header = child 

235 elif isinstance(child, TableBodyElement): 

236 self.body = child 

237 else: 

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

239 return False 

240 

241 def __rich_console__( 

242 self, console: Console, options: ConsoleOptions 

243 ) -> RenderResult: 

244 table = Table(box=box.SIMPLE_HEAVY) 

245 

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

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

248 table.add_column(column.content) 

249 

250 if self.body is not None: 

251 for row in self.body.rows: 

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

253 table.add_row(*row_content) 

254 

255 yield table 

256 

257 

258class TableHeaderElement(MarkdownElement): 

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

260 

261 def __init__(self) -> None: 

262 self.row: TableRowElement | None = None 

263 

264 def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: 

265 assert isinstance(child, TableRowElement) 

266 self.row = child 

267 return False 

268 

269 

270class TableBodyElement(MarkdownElement): 

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

272 

273 def __init__(self) -> None: 

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

275 

276 def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: 

277 assert isinstance(child, TableRowElement) 

278 self.rows.append(child) 

279 return False 

280 

281 

282class TableRowElement(MarkdownElement): 

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

284 

285 def __init__(self) -> None: 

286 self.cells: list[TableDataElement] = [] 

287 

288 def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: 

289 assert isinstance(child, TableDataElement) 

290 self.cells.append(child) 

291 return False 

292 

293 

294class TableDataElement(MarkdownElement): 

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

296 and `th_open` and `th_close`.""" 

297 

298 @classmethod 

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

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

301 

302 justify: JustifyMethod 

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

304 justify = "right" 

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

306 justify = "center" 

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

308 justify = "left" 

309 else: 

310 justify = "default" 

311 

312 assert justify in get_args(JustifyMethod) 

313 return cls(justify=justify) 

314 

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

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

317 self.justify = justify 

318 

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

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

321 text.stylize(context.current_style) 

322 self.content.append_text(text) 

323 

324 

325class ListElement(MarkdownElement): 

326 """A list element.""" 

327 

328 @classmethod 

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

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

331 

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

333 self.items: list[ListItem] = [] 

334 self.list_type = list_type 

335 self.list_start = list_start 

336 

337 def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: 

338 assert isinstance(child, ListItem) 

339 self.items.append(child) 

340 return False 

341 

342 def __rich_console__( 

343 self, console: Console, options: ConsoleOptions 

344 ) -> RenderResult: 

345 if self.list_type == "bullet_list_open": 

346 for item in self.items: 

347 yield from item.render_bullet(console, options) 

348 else: 

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

350 last_number = number + len(self.items) 

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

352 yield from item.render_number( 

353 console, options, number + index, last_number 

354 ) 

355 

356 

357class ListItem(TextElement): 

358 """An item in a list.""" 

359 

360 style_name = "markdown.item" 

361 

362 def __init__(self) -> None: 

363 self.elements: Renderables = Renderables() 

364 

365 def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: 

366 self.elements.append(child) 

367 return False 

368 

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

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

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

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

373 

374 bullet = Segment(" • ", bullet_style) 

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

376 new_line = Segment("\n") 

377 for first, line in loop_first(lines): 

378 yield bullet if first else padding 

379 yield from line 

380 yield new_line 

381 

382 def render_number( 

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

384 ) -> RenderResult: 

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

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

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

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

389 

390 new_line = Segment("\n") 

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

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

393 for first, line in loop_first(lines): 

394 yield numeral if first else padding 

395 yield from line 

396 yield new_line 

397 

398 

399class Link(TextElement): 

400 @classmethod 

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

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

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

404 

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

406 self.text = Text(text) 

407 self.href = href 

408 

409 

410class ImageItem(TextElement): 

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

412 

413 new_line = False 

414 

415 @classmethod 

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

417 """Factory to create markdown element, 

418 

419 Args: 

420 markdown (Markdown): The parent Markdown object. 

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

422 

423 Returns: 

424 MarkdownElement: A new markdown element 

425 """ 

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

427 

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

429 self.destination = destination 

430 self.hyperlinks = hyperlinks 

431 self.link: str | None = None 

432 super().__init__() 

433 

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

435 self.link = context.current_style.link 

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

437 super().on_enter(context) 

438 

439 def __rich_console__( 

440 self, console: Console, options: ConsoleOptions 

441 ) -> RenderResult: 

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

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

444 if self.hyperlinks: 

445 title.stylize(link_style) 

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

447 yield text 

448 

449 

450class MarkdownContext: 

451 """Manages the console render state.""" 

452 

453 def __init__( 

454 self, 

455 console: Console, 

456 options: ConsoleOptions, 

457 style: Style, 

458 inline_code_lexer: str | None = None, 

459 inline_code_theme: str = "monokai", 

460 ) -> None: 

461 self.console = console 

462 self.options = options 

463 self.style_stack: StyleStack = StyleStack(style) 

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

465 

466 self._syntax: Syntax | None = None 

467 if inline_code_lexer is not None: 

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

469 

470 @property 

471 def current_style(self) -> Style: 

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

473 return self.style_stack.current 

474 

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

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

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

478 highlight_text = self._syntax.highlight(text) 

479 highlight_text.rstrip() 

480 self.stack.top.on_text( 

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

482 ) 

483 else: 

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

485 

486 def enter_style(self, style_name: str | Style) -> Style: 

487 """Enter a style context.""" 

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

489 self.style_stack.push(style) 

490 return self.current_style 

491 

492 def leave_style(self) -> Style: 

493 """Leave a style context.""" 

494 style = self.style_stack.pop() 

495 return style 

496 

497 

498class Markdown(JupyterMixin): 

499 """A Markdown renderable. 

500 

501 Args: 

502 markup (str): A string containing markdown. 

503 code_theme (str, optional): Pygments theme for code blocks. Defaults to "monokai". See https://pygments.org/styles/ for code themes. 

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

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

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

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

508 enabled. Defaults to None. 

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

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

511 """ 

512 

513 elements: ClassVar[dict[str, type[MarkdownElement]]] = { 

514 "paragraph_open": Paragraph, 

515 "heading_open": Heading, 

516 "fence": CodeBlock, 

517 "code_block": CodeBlock, 

518 "blockquote_open": BlockQuote, 

519 "hr": HorizontalRule, 

520 "bullet_list_open": ListElement, 

521 "ordered_list_open": ListElement, 

522 "list_item_open": ListItem, 

523 "image": ImageItem, 

524 "table_open": TableElement, 

525 "tbody_open": TableBodyElement, 

526 "thead_open": TableHeaderElement, 

527 "tr_open": TableRowElement, 

528 "td_open": TableDataElement, 

529 "th_open": TableDataElement, 

530 } 

531 

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

533 

534 def __init__( 

535 self, 

536 markup: str, 

537 code_theme: str = "monokai", 

538 justify: JustifyMethod | None = None, 

539 style: str | Style = "none", 

540 hyperlinks: bool = True, 

541 inline_code_lexer: str | None = None, 

542 inline_code_theme: str | None = None, 

543 ) -> None: 

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

545 self.markup = markup 

546 self.parsed = parser.parse(markup) 

547 self.code_theme = code_theme 

548 self.justify: JustifyMethod | None = justify 

549 self.style = style 

550 self.hyperlinks = hyperlinks 

551 self.inline_code_lexer = inline_code_lexer 

552 self.inline_code_theme = inline_code_theme or code_theme 

553 

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

555 """Flattens the token stream.""" 

556 for token in tokens: 

557 is_fence = token.type == "fence" 

558 is_image = token.tag == "img" 

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

560 yield from self._flatten_tokens(token.children) 

561 else: 

562 yield token 

563 

564 def __rich_console__( 

565 self, console: Console, options: ConsoleOptions 

566 ) -> RenderResult: 

567 """Render markdown to the console.""" 

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

569 options = options.update(height=None) 

570 context = MarkdownContext( 

571 console, 

572 options, 

573 style, 

574 inline_code_lexer=self.inline_code_lexer, 

575 inline_code_theme=self.inline_code_theme, 

576 ) 

577 tokens = self.parsed 

578 inline_style_tags = self.inlines 

579 new_line = False 

580 _new_line_segment = Segment.line() 

581 

582 for token in self._flatten_tokens(tokens): 

583 node_type = token.type 

584 tag = token.tag 

585 

586 entering = token.nesting == 1 

587 exiting = token.nesting == -1 

588 self_closing = token.nesting == 0 

589 

590 if node_type == "text": 

591 context.on_text(token.content, node_type) 

592 elif node_type == "hardbreak": 

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

594 elif node_type == "softbreak": 

595 context.on_text(" ", node_type) 

596 elif node_type == "link_open": 

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

598 if self.hyperlinks: 

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

600 link_style += Style(link=href) 

601 context.enter_style(link_style) 

602 else: 

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

604 elif node_type == "link_close": 

605 if self.hyperlinks: 

606 context.leave_style() 

607 else: 

608 element = context.stack.pop() 

609 assert isinstance(element, Link) 

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

611 context.enter_style(link_style) 

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

613 context.leave_style() 

614 context.on_text(" (", node_type) 

615 link_url_style = console.get_style( 

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

617 ) 

618 context.enter_style(link_url_style) 

619 context.on_text(element.href, node_type) 

620 context.leave_style() 

621 context.on_text(")", node_type) 

622 elif ( 

623 tag in inline_style_tags 

624 and node_type != "fence" 

625 and node_type != "code_block" 

626 ): 

627 if entering: 

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

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

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

631 elif exiting: 

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

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

634 context.leave_style() 

635 else: 

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

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

638 if token.content: 

639 context.on_text(token.content, node_type) 

640 context.leave_style() 

641 else: 

642 # Map the markdown tag -> MarkdownElement renderable 

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

644 element = element_class.create(self, token) 

645 

646 if entering or self_closing: 

647 context.stack.push(element) 

648 element.on_enter(context) 

649 

650 if exiting: # CLOSING tag 

651 element = context.stack.pop() 

652 

653 should_render = not context.stack or ( 

654 context.stack 

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

656 ) 

657 

658 if should_render: 

659 if new_line: 

660 yield _new_line_segment 

661 

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

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

664 context.stack.pop() 

665 text = token.content 

666 if text is not None: 

667 element.on_text(context, text) 

668 

669 should_render = ( 

670 not context.stack 

671 or context.stack 

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

673 ) 

674 if should_render: 

675 if new_line and node_type != "inline": 

676 yield _new_line_segment 

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

678 

679 if exiting or self_closing: 

680 element.on_leave(context) 

681 new_line = element.new_line 

682 

683 

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

685 import argparse 

686 import sys 

687 

688 parser = argparse.ArgumentParser( 

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

690 ) 

691 parser.add_argument( 

692 "path", 

693 metavar="PATH", 

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

695 ) 

696 parser.add_argument( 

697 "-c", 

698 "--force-color", 

699 dest="force_color", 

700 action="store_true", 

701 default=None, 

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

703 ) 

704 parser.add_argument( 

705 "-t", 

706 "--code-theme", 

707 dest="code_theme", 

708 default="monokai", 

709 help="pygments code theme", 

710 ) 

711 parser.add_argument( 

712 "-i", 

713 "--inline-code-lexer", 

714 dest="inline_code_lexer", 

715 default=None, 

716 help="inline_code_lexer", 

717 ) 

718 parser.add_argument( 

719 "-y", 

720 "--hyperlinks", 

721 dest="hyperlinks", 

722 action="store_true", 

723 help="enable hyperlinks", 

724 ) 

725 parser.add_argument( 

726 "-w", 

727 "--width", 

728 type=int, 

729 dest="width", 

730 default=None, 

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

732 ) 

733 parser.add_argument( 

734 "-j", 

735 "--justify", 

736 dest="justify", 

737 action="store_true", 

738 help="enable full text justify", 

739 ) 

740 parser.add_argument( 

741 "-p", 

742 "--page", 

743 dest="page", 

744 action="store_true", 

745 help="use pager to scroll output", 

746 ) 

747 args = parser.parse_args() 

748 

749 from rich.console import Console 

750 

751 if args.path == "-": 

752 markdown_body = sys.stdin.read() 

753 else: 

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

755 markdown_body = markdown_file.read() 

756 

757 markdown = Markdown( 

758 markdown_body, 

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

760 code_theme=args.code_theme, 

761 hyperlinks=args.hyperlinks, 

762 inline_code_lexer=args.inline_code_lexer, 

763 ) 

764 if args.page: 

765 import io 

766 import pydoc 

767 

768 fileio = io.StringIO() 

769 console = Console( 

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

771 ) 

772 console.print(markdown) 

773 pydoc.pager(fileio.getvalue()) 

774 

775 else: 

776 console = Console( 

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

778 ) 

779 console.print(markdown)