summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/rich/markdown.py
diff options
context:
space:
mode:
authorcyfraeviolae <cyfraeviolae>2024-04-03 03:10:44 -0400
committercyfraeviolae <cyfraeviolae>2024-04-03 03:10:44 -0400
commit6d7ba58f880be618ade07f8ea080fe8c4bf8a896 (patch)
treeb1c931051ffcebd2bd9d61d98d6233ffa289bbce /venv/lib/python3.11/site-packages/rich/markdown.py
parent4f884c9abc32990b4061a1bb6997b4b37e58ea0b (diff)
venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/rich/markdown.py')
-rw-r--r--venv/lib/python3.11/site-packages/rich/markdown.py800
1 files changed, 800 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/rich/markdown.py b/venv/lib/python3.11/site-packages/rich/markdown.py
new file mode 100644
index 0000000..9b5ceac
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/rich/markdown.py
@@ -0,0 +1,800 @@
+from __future__ import annotations
+
+import sys
+from typing import ClassVar, Dict, Iterable, List, Optional, Type, Union
+
+from markdown_it import MarkdownIt
+from markdown_it.token import Token
+
+if sys.version_info >= (3, 8):
+ from typing import get_args
+else:
+ from typing_extensions import get_args # pragma: no cover
+
+from rich.table import Table
+
+from . import box
+from ._loop import loop_first
+from ._stack import Stack
+from .console import Console, ConsoleOptions, JustifyMethod, RenderResult
+from .containers import Renderables
+from .jupyter import JupyterMixin
+from .panel import Panel
+from .rule import Rule
+from .segment import Segment
+from .style import Style, StyleStack
+from .syntax import Syntax
+from .text import Text, TextType
+
+
+class MarkdownElement:
+ new_line: ClassVar[bool] = True
+
+ @classmethod
+ def create(cls, markdown: "Markdown", token: Token) -> "MarkdownElement":
+ """Factory to create markdown element,
+
+ Args:
+ markdown (Markdown): The parent Markdown object.
+ token (Token): A node from markdown-it.
+
+ Returns:
+ MarkdownElement: A new markdown element
+ """
+ return cls()
+
+ def on_enter(self, context: "MarkdownContext") -> None:
+ """Called when the node is entered.
+
+ Args:
+ context (MarkdownContext): The markdown context.
+ """
+
+ def on_text(self, context: "MarkdownContext", text: TextType) -> None:
+ """Called when text is parsed.
+
+ Args:
+ context (MarkdownContext): The markdown context.
+ """
+
+ def on_leave(self, context: "MarkdownContext") -> None:
+ """Called when the parser leaves the element.
+
+ Args:
+ context (MarkdownContext): [description]
+ """
+
+ def on_child_close(
+ self, context: "MarkdownContext", child: "MarkdownElement"
+ ) -> bool:
+ """Called when a child element is closed.
+
+ This method allows a parent element to take over rendering of its children.
+
+ Args:
+ context (MarkdownContext): The markdown context.
+ child (MarkdownElement): The child markdown element.
+
+ Returns:
+ bool: Return True to render the element, or False to not render the element.
+ """
+ return True
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ return ()
+
+
+class UnknownElement(MarkdownElement):
+ """An unknown element.
+
+ Hopefully there will be no unknown elements, and we will have a MarkdownElement for
+ everything in the document.
+
+ """
+
+
+class TextElement(MarkdownElement):
+ """Base class for elements that render text."""
+
+ style_name = "none"
+
+ def on_enter(self, context: "MarkdownContext") -> None:
+ self.style = context.enter_style(self.style_name)
+ self.text = Text(justify="left")
+
+ def on_text(self, context: "MarkdownContext", text: TextType) -> None:
+ self.text.append(text, context.current_style if isinstance(text, str) else None)
+
+ def on_leave(self, context: "MarkdownContext") -> None:
+ context.leave_style()
+
+
+class Paragraph(TextElement):
+ """A Paragraph."""
+
+ style_name = "markdown.paragraph"
+ justify: JustifyMethod
+
+ @classmethod
+ def create(cls, markdown: "Markdown", token: Token) -> "Paragraph":
+ return cls(justify=markdown.justify or "left")
+
+ def __init__(self, justify: JustifyMethod) -> None:
+ self.justify = justify
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ self.text.justify = self.justify
+ yield self.text
+
+
+class Heading(TextElement):
+ """A heading."""
+
+ @classmethod
+ def create(cls, markdown: "Markdown", token: Token) -> "Heading":
+ return cls(token.tag)
+
+ def on_enter(self, context: "MarkdownContext") -> None:
+ self.text = Text()
+ context.enter_style(self.style_name)
+
+ def __init__(self, tag: str) -> None:
+ self.tag = tag
+ self.style_name = f"markdown.{tag}"
+ super().__init__()
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ text = self.text
+ text.justify = "center"
+ if self.tag == "h1":
+ # Draw a border around h1s
+ yield Panel(
+ text,
+ box=box.HEAVY,
+ style="markdown.h1.border",
+ )
+ else:
+ # Styled text for h2 and beyond
+ if self.tag == "h2":
+ yield Text("")
+ yield text
+
+
+class CodeBlock(TextElement):
+ """A code block with syntax highlighting."""
+
+ style_name = "markdown.code_block"
+
+ @classmethod
+ def create(cls, markdown: "Markdown", token: Token) -> "CodeBlock":
+ node_info = token.info or ""
+ lexer_name = node_info.partition(" ")[0]
+ return cls(lexer_name or "text", markdown.code_theme)
+
+ def __init__(self, lexer_name: str, theme: str) -> None:
+ self.lexer_name = lexer_name
+ self.theme = theme
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ code = str(self.text).rstrip()
+ syntax = Syntax(
+ code, self.lexer_name, theme=self.theme, word_wrap=True, padding=1
+ )
+ yield syntax
+
+
+class BlockQuote(TextElement):
+ """A block quote."""
+
+ style_name = "markdown.block_quote"
+
+ def __init__(self) -> None:
+ self.elements: Renderables = Renderables()
+
+ def on_child_close(
+ self, context: "MarkdownContext", child: "MarkdownElement"
+ ) -> bool:
+ self.elements.append(child)
+ return False
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ render_options = options.update(width=options.max_width - 4)
+ lines = console.render_lines(self.elements, render_options, style=self.style)
+ style = self.style
+ new_line = Segment("\n")
+ padding = Segment("▌ ", style)
+ for line in lines:
+ yield padding
+ yield from line
+ yield new_line
+
+
+class HorizontalRule(MarkdownElement):
+ """A horizontal rule to divide sections."""
+
+ new_line = False
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ style = console.get_style("markdown.hr", default="none")
+ yield Rule(style=style)
+
+
+class TableElement(MarkdownElement):
+ """MarkdownElement corresponding to `table_open`."""
+
+ def __init__(self) -> None:
+ self.header: TableHeaderElement | None = None
+ self.body: TableBodyElement | None = None
+
+ def on_child_close(
+ self, context: "MarkdownContext", child: "MarkdownElement"
+ ) -> bool:
+ if isinstance(child, TableHeaderElement):
+ self.header = child
+ elif isinstance(child, TableBodyElement):
+ self.body = child
+ else:
+ raise RuntimeError("Couldn't process markdown table.")
+ return False
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ table = Table(box=box.SIMPLE_HEAVY)
+
+ if self.header is not None and self.header.row is not None:
+ for column in self.header.row.cells:
+ table.add_column(column.content)
+
+ if self.body is not None:
+ for row in self.body.rows:
+ row_content = [element.content for element in row.cells]
+ table.add_row(*row_content)
+
+ yield table
+
+
+class TableHeaderElement(MarkdownElement):
+ """MarkdownElement corresponding to `thead_open` and `thead_close`."""
+
+ def __init__(self) -> None:
+ self.row: TableRowElement | None = None
+
+ def on_child_close(
+ self, context: "MarkdownContext", child: "MarkdownElement"
+ ) -> bool:
+ assert isinstance(child, TableRowElement)
+ self.row = child
+ return False
+
+
+class TableBodyElement(MarkdownElement):
+ """MarkdownElement corresponding to `tbody_open` and `tbody_close`."""
+
+ def __init__(self) -> None:
+ self.rows: list[TableRowElement] = []
+
+ def on_child_close(
+ self, context: "MarkdownContext", child: "MarkdownElement"
+ ) -> bool:
+ assert isinstance(child, TableRowElement)
+ self.rows.append(child)
+ return False
+
+
+class TableRowElement(MarkdownElement):
+ """MarkdownElement corresponding to `tr_open` and `tr_close`."""
+
+ def __init__(self) -> None:
+ self.cells: List[TableDataElement] = []
+
+ def on_child_close(
+ self, context: "MarkdownContext", child: "MarkdownElement"
+ ) -> bool:
+ assert isinstance(child, TableDataElement)
+ self.cells.append(child)
+ return False
+
+
+class TableDataElement(MarkdownElement):
+ """MarkdownElement corresponding to `td_open` and `td_close`
+ and `th_open` and `th_close`."""
+
+ @classmethod
+ def create(cls, markdown: "Markdown", token: Token) -> "MarkdownElement":
+ style = str(token.attrs.get("style")) or ""
+
+ justify: JustifyMethod
+ if "text-align:right" in style:
+ justify = "right"
+ elif "text-align:center" in style:
+ justify = "center"
+ elif "text-align:left" in style:
+ justify = "left"
+ else:
+ justify = "default"
+
+ assert justify in get_args(JustifyMethod)
+ return cls(justify=justify)
+
+ def __init__(self, justify: JustifyMethod) -> None:
+ self.content: Text = Text("", justify=justify)
+ self.justify = justify
+
+ def on_text(self, context: "MarkdownContext", text: TextType) -> None:
+ text = Text(text) if isinstance(text, str) else text
+ text.stylize(context.current_style)
+ self.content.append_text(text)
+
+
+class ListElement(MarkdownElement):
+ """A list element."""
+
+ @classmethod
+ def create(cls, markdown: "Markdown", token: Token) -> "ListElement":
+ return cls(token.type, int(token.attrs.get("start", 1)))
+
+ def __init__(self, list_type: str, list_start: int | None) -> None:
+ self.items: List[ListItem] = []
+ self.list_type = list_type
+ self.list_start = list_start
+
+ def on_child_close(
+ self, context: "MarkdownContext", child: "MarkdownElement"
+ ) -> bool:
+ assert isinstance(child, ListItem)
+ self.items.append(child)
+ return False
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ if self.list_type == "bullet_list_open":
+ for item in self.items:
+ yield from item.render_bullet(console, options)
+ else:
+ number = 1 if self.list_start is None else self.list_start
+ last_number = number + len(self.items)
+ for index, item in enumerate(self.items):
+ yield from item.render_number(
+ console, options, number + index, last_number
+ )
+
+
+class ListItem(TextElement):
+ """An item in a list."""
+
+ style_name = "markdown.item"
+
+ def __init__(self) -> None:
+ self.elements: Renderables = Renderables()
+
+ def on_child_close(
+ self, context: "MarkdownContext", child: "MarkdownElement"
+ ) -> bool:
+ self.elements.append(child)
+ return False
+
+ def render_bullet(self, console: Console, options: ConsoleOptions) -> RenderResult:
+ render_options = options.update(width=options.max_width - 3)
+ lines = console.render_lines(self.elements, render_options, style=self.style)
+ bullet_style = console.get_style("markdown.item.bullet", default="none")
+
+ bullet = Segment(" • ", bullet_style)
+ padding = Segment(" " * 3, bullet_style)
+ new_line = Segment("\n")
+ for first, line in loop_first(lines):
+ yield bullet if first else padding
+ yield from line
+ yield new_line
+
+ def render_number(
+ self, console: Console, options: ConsoleOptions, number: int, last_number: int
+ ) -> RenderResult:
+ number_width = len(str(last_number)) + 2
+ render_options = options.update(width=options.max_width - number_width)
+ lines = console.render_lines(self.elements, render_options, style=self.style)
+ number_style = console.get_style("markdown.item.number", default="none")
+
+ new_line = Segment("\n")
+ padding = Segment(" " * number_width, number_style)
+ numeral = Segment(f"{number}".rjust(number_width - 1) + " ", number_style)
+ for first, line in loop_first(lines):
+ yield numeral if first else padding
+ yield from line
+ yield new_line
+
+
+class Link(TextElement):
+ @classmethod
+ def create(cls, markdown: "Markdown", token: Token) -> "MarkdownElement":
+ url = token.attrs.get("href", "#")
+ return cls(token.content, str(url))
+
+ def __init__(self, text: str, href: str):
+ self.text = Text(text)
+ self.href = href
+
+
+class ImageItem(TextElement):
+ """Renders a placeholder for an image."""
+
+ new_line = False
+
+ @classmethod
+ def create(cls, markdown: "Markdown", token: Token) -> "MarkdownElement":
+ """Factory to create markdown element,
+
+ Args:
+ markdown (Markdown): The parent Markdown object.
+ token (Any): A token from markdown-it.
+
+ Returns:
+ MarkdownElement: A new markdown element
+ """
+ return cls(str(token.attrs.get("src", "")), markdown.hyperlinks)
+
+ def __init__(self, destination: str, hyperlinks: bool) -> None:
+ self.destination = destination
+ self.hyperlinks = hyperlinks
+ self.link: Optional[str] = None
+ super().__init__()
+
+ def on_enter(self, context: "MarkdownContext") -> None:
+ self.link = context.current_style.link
+ self.text = Text(justify="left")
+ super().on_enter(context)
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ link_style = Style(link=self.link or self.destination or None)
+ title = self.text or Text(self.destination.strip("/").rsplit("/", 1)[-1])
+ if self.hyperlinks:
+ title.stylize(link_style)
+ text = Text.assemble("🌆 ", title, " ", end="")
+ yield text
+
+
+class MarkdownContext:
+ """Manages the console render state."""
+
+ def __init__(
+ self,
+ console: Console,
+ options: ConsoleOptions,
+ style: Style,
+ inline_code_lexer: Optional[str] = None,
+ inline_code_theme: str = "monokai",
+ ) -> None:
+ self.console = console
+ self.options = options
+ self.style_stack: StyleStack = StyleStack(style)
+ self.stack: Stack[MarkdownElement] = Stack()
+
+ self._syntax: Optional[Syntax] = None
+ if inline_code_lexer is not None:
+ self._syntax = Syntax("", inline_code_lexer, theme=inline_code_theme)
+
+ @property
+ def current_style(self) -> Style:
+ """Current style which is the product of all styles on the stack."""
+ return self.style_stack.current
+
+ def on_text(self, text: str, node_type: str) -> None:
+ """Called when the parser visits text."""
+ if node_type in {"fence", "code_inline"} and self._syntax is not None:
+ highlight_text = self._syntax.highlight(text)
+ highlight_text.rstrip()
+ self.stack.top.on_text(
+ self, Text.assemble(highlight_text, style=self.style_stack.current)
+ )
+ else:
+ self.stack.top.on_text(self, text)
+
+ def enter_style(self, style_name: Union[str, Style]) -> Style:
+ """Enter a style context."""
+ style = self.console.get_style(style_name, default="none")
+ self.style_stack.push(style)
+ return self.current_style
+
+ def leave_style(self) -> Style:
+ """Leave a style context."""
+ style = self.style_stack.pop()
+ return style
+
+
+class Markdown(JupyterMixin):
+ """A Markdown renderable.
+
+ Args:
+ markup (str): A string containing markdown.
+ code_theme (str, optional): Pygments theme for code blocks. Defaults to "monokai".
+ justify (JustifyMethod, optional): Justify value for paragraphs. Defaults to None.
+ style (Union[str, Style], optional): Optional style to apply to markdown.
+ hyperlinks (bool, optional): Enable hyperlinks. Defaults to ``True``.
+ inline_code_lexer: (str, optional): Lexer to use if inline code highlighting is
+ enabled. Defaults to None.
+ inline_code_theme: (Optional[str], optional): Pygments theme for inline code
+ highlighting, or None for no highlighting. Defaults to None.
+ """
+
+ elements: ClassVar[Dict[str, Type[MarkdownElement]]] = {
+ "paragraph_open": Paragraph,
+ "heading_open": Heading,
+ "fence": CodeBlock,
+ "code_block": CodeBlock,
+ "blockquote_open": BlockQuote,
+ "hr": HorizontalRule,
+ "bullet_list_open": ListElement,
+ "ordered_list_open": ListElement,
+ "list_item_open": ListItem,
+ "image": ImageItem,
+ "table_open": TableElement,
+ "tbody_open": TableBodyElement,
+ "thead_open": TableHeaderElement,
+ "tr_open": TableRowElement,
+ "td_open": TableDataElement,
+ "th_open": TableDataElement,
+ }
+
+ inlines = {"em", "strong", "code", "s"}
+
+ def __init__(
+ self,
+ markup: str,
+ code_theme: str = "monokai",
+ justify: Optional[JustifyMethod] = None,
+ style: Union[str, Style] = "none",
+ hyperlinks: bool = True,
+ inline_code_lexer: Optional[str] = None,
+ inline_code_theme: Optional[str] = None,
+ ) -> None:
+ parser = MarkdownIt().enable("strikethrough").enable("table")
+ self.markup = markup
+ self.parsed = parser.parse(markup)
+ self.code_theme = code_theme
+ self.justify: Optional[JustifyMethod] = justify
+ self.style = style
+ self.hyperlinks = hyperlinks
+ self.inline_code_lexer = inline_code_lexer
+ self.inline_code_theme = inline_code_theme or code_theme
+
+ def _flatten_tokens(self, tokens: Iterable[Token]) -> Iterable[Token]:
+ """Flattens the token stream."""
+ for token in tokens:
+ is_fence = token.type == "fence"
+ is_image = token.tag == "img"
+ if token.children and not (is_image or is_fence):
+ yield from self._flatten_tokens(token.children)
+ else:
+ yield token
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ """Render markdown to the console."""
+ style = console.get_style(self.style, default="none")
+ options = options.update(height=None)
+ context = MarkdownContext(
+ console,
+ options,
+ style,
+ inline_code_lexer=self.inline_code_lexer,
+ inline_code_theme=self.inline_code_theme,
+ )
+ tokens = self.parsed
+ inline_style_tags = self.inlines
+ new_line = False
+ _new_line_segment = Segment.line()
+
+ for token in self._flatten_tokens(tokens):
+ node_type = token.type
+ tag = token.tag
+
+ entering = token.nesting == 1
+ exiting = token.nesting == -1
+ self_closing = token.nesting == 0
+
+ if node_type == "text":
+ context.on_text(token.content, node_type)
+ elif node_type == "hardbreak":
+ context.on_text("\n", node_type)
+ elif node_type == "softbreak":
+ context.on_text(" ", node_type)
+ elif node_type == "link_open":
+ href = str(token.attrs.get("href", ""))
+ if self.hyperlinks:
+ link_style = console.get_style("markdown.link_url", default="none")
+ link_style += Style(link=href)
+ context.enter_style(link_style)
+ else:
+ context.stack.push(Link.create(self, token))
+ elif node_type == "link_close":
+ if self.hyperlinks:
+ context.leave_style()
+ else:
+ element = context.stack.pop()
+ assert isinstance(element, Link)
+ link_style = console.get_style("markdown.link", default="none")
+ context.enter_style(link_style)
+ context.on_text(element.text.plain, node_type)
+ context.leave_style()
+ context.on_text(" (", node_type)
+ link_url_style = console.get_style(
+ "markdown.link_url", default="none"
+ )
+ context.enter_style(link_url_style)
+ context.on_text(element.href, node_type)
+ context.leave_style()
+ context.on_text(")", node_type)
+ elif (
+ tag in inline_style_tags
+ and node_type != "fence"
+ and node_type != "code_block"
+ ):
+ if entering:
+ # If it's an opening inline token e.g. strong, em, etc.
+ # Then we move into a style context i.e. push to stack.
+ context.enter_style(f"markdown.{tag}")
+ elif exiting:
+ # If it's a closing inline style, then we pop the style
+ # off of the stack, to move out of the context of it...
+ context.leave_style()
+ else:
+ # If it's a self-closing inline style e.g. `code_inline`
+ context.enter_style(f"markdown.{tag}")
+ if token.content:
+ context.on_text(token.content, node_type)
+ context.leave_style()
+ else:
+ # Map the markdown tag -> MarkdownElement renderable
+ element_class = self.elements.get(token.type) or UnknownElement
+ element = element_class.create(self, token)
+
+ if entering or self_closing:
+ context.stack.push(element)
+ element.on_enter(context)
+
+ if exiting: # CLOSING tag
+ element = context.stack.pop()
+
+ should_render = not context.stack or (
+ context.stack
+ and context.stack.top.on_child_close(context, element)
+ )
+
+ if should_render:
+ if new_line:
+ yield _new_line_segment
+
+ yield from console.render(element, context.options)
+ elif self_closing: # SELF-CLOSING tags (e.g. text, code, image)
+ context.stack.pop()
+ text = token.content
+ if text is not None:
+ element.on_text(context, text)
+
+ should_render = (
+ not context.stack
+ or context.stack
+ and context.stack.top.on_child_close(context, element)
+ )
+ if should_render:
+ if new_line:
+ yield _new_line_segment
+ yield from console.render(element, context.options)
+
+ if exiting or self_closing:
+ element.on_leave(context)
+ new_line = element.new_line
+
+
+if __name__ == "__main__": # pragma: no cover
+ import argparse
+ import sys
+
+ parser = argparse.ArgumentParser(
+ description="Render Markdown to the console with Rich"
+ )
+ parser.add_argument(
+ "path",
+ metavar="PATH",
+ help="path to markdown file, or - for stdin",
+ )
+ parser.add_argument(
+ "-c",
+ "--force-color",
+ dest="force_color",
+ action="store_true",
+ default=None,
+ help="force color for non-terminals",
+ )
+ parser.add_argument(
+ "-t",
+ "--code-theme",
+ dest="code_theme",
+ default="monokai",
+ help="pygments code theme",
+ )
+ parser.add_argument(
+ "-i",
+ "--inline-code-lexer",
+ dest="inline_code_lexer",
+ default=None,
+ help="inline_code_lexer",
+ )
+ parser.add_argument(
+ "-y",
+ "--hyperlinks",
+ dest="hyperlinks",
+ action="store_true",
+ help="enable hyperlinks",
+ )
+ parser.add_argument(
+ "-w",
+ "--width",
+ type=int,
+ dest="width",
+ default=None,
+ help="width of output (default will auto-detect)",
+ )
+ parser.add_argument(
+ "-j",
+ "--justify",
+ dest="justify",
+ action="store_true",
+ help="enable full text justify",
+ )
+ parser.add_argument(
+ "-p",
+ "--page",
+ dest="page",
+ action="store_true",
+ help="use pager to scroll output",
+ )
+ args = parser.parse_args()
+
+ from rich.console import Console
+
+ if args.path == "-":
+ markdown_body = sys.stdin.read()
+ else:
+ with open(args.path, "rt", encoding="utf-8") as markdown_file:
+ markdown_body = markdown_file.read()
+
+ markdown = Markdown(
+ markdown_body,
+ justify="full" if args.justify else "left",
+ code_theme=args.code_theme,
+ hyperlinks=args.hyperlinks,
+ inline_code_lexer=args.inline_code_lexer,
+ )
+ if args.page:
+ import io
+ import pydoc
+
+ fileio = io.StringIO()
+ console = Console(
+ file=fileio, force_terminal=args.force_color, width=args.width
+ )
+ console.print(markdown)
+ pydoc.pager(fileio.getvalue())
+
+ else:
+ console = Console(
+ force_terminal=args.force_color, width=args.width, record=True
+ )
+ console.print(markdown)