127 lines
4.1 KiB
Python
127 lines
4.1 KiB
Python
from typing import Iterable, List, Optional, Sequence
|
|
|
|
from .compat import Literal
|
|
|
|
|
|
class MarkdownRenderer:
|
|
"""Simple helper for generating raw Markdown."""
|
|
|
|
def __init__(self, no_emoji: bool = False):
|
|
"""Initialize the renderer.
|
|
|
|
no_emoji (bool): Don't show emoji in titles etc.
|
|
"""
|
|
self.data: List = []
|
|
self.no_emoji = no_emoji
|
|
|
|
@property
|
|
def text(self) -> str:
|
|
"""RETURNS (str): The Markdown document."""
|
|
return "\n\n".join(self.data)
|
|
|
|
def add(self, content: str):
|
|
"""Add a string to the Markdown document.
|
|
|
|
content (str): Add content to the document.
|
|
"""
|
|
self.data.append(content)
|
|
|
|
def table(
|
|
self,
|
|
data: Iterable[Iterable[str]],
|
|
header: Sequence[str],
|
|
aligns: Optional[Sequence[Literal["r", "c", "l"]]] = None,
|
|
) -> str:
|
|
"""Create a Markdown table.
|
|
|
|
data (Iterable[Iterable[str]]): The body, one iterable per row,
|
|
containig an interable of column contents.
|
|
header (Sequence[str]): The column names.
|
|
aligns (Optional[Sequence[Literal["r", "c", "l"]]]): Optional alignment-mode for each column. Values should
|
|
either be 'l' (left), 'r' (right), or 'c' (center). Optional.
|
|
RETURNS (str): The rendered table.
|
|
"""
|
|
if aligns is None:
|
|
aligns = ["l"] * len(header)
|
|
if len(aligns) != len(header):
|
|
err = "Invalid aligns: {} (header length: {})".format(aligns, len(header))
|
|
raise ValueError(err)
|
|
get_divider = lambda a: ":---:" if a == "c" else "---:" if a == "r" else "---"
|
|
head = "| {} |".format(" | ".join(header))
|
|
divider = "| {} |".format(
|
|
" | ".join(get_divider(aligns[i]) for i in range(len(header)))
|
|
)
|
|
body = "\n".join("| {} |".format(" | ".join(row)) for row in data)
|
|
return "{}\n{}\n{}".format(head, divider, body)
|
|
|
|
def title(self, level: int, text: str, emoji: Optional[str] = None) -> str:
|
|
"""Create a Markdown heading.
|
|
|
|
level (int): The heading level, e.g. 3 for ###
|
|
text (str): The heading text.
|
|
emoji (Optional[str]): Optional emoji to show before heading text, if enabled.
|
|
RETURNS (str): The rendered title.
|
|
"""
|
|
prefix = "{} ".format(emoji) if emoji and not self.no_emoji else ""
|
|
return "{} {}{}".format("#" * level, prefix, text)
|
|
|
|
def list(self, items: Iterable[str], numbered: bool = False) -> str:
|
|
"""Create a non-nested list.
|
|
|
|
items (Iterable[str]): The list items.
|
|
numbered (bool): Whether to use a numbered list.
|
|
RETURNS (str): The rendered list.
|
|
"""
|
|
content = []
|
|
for i, item in enumerate(items):
|
|
if numbered:
|
|
content.append("{}. {}".format(i + 1, item))
|
|
else:
|
|
content.append("- {}".format(item))
|
|
return "\n".join(content)
|
|
|
|
def link(self, text: str, url: str) -> str:
|
|
"""Create a Markdown link.
|
|
|
|
text (str): The link text.
|
|
url (str): The link URL.
|
|
RETURNS (str): The rendered link.
|
|
"""
|
|
return "[{}]({})".format(text, url)
|
|
|
|
def code_block(self, text: str, lang: str = "") -> str:
|
|
"""Create a Markdown code block.
|
|
|
|
text (str): The code text.
|
|
lang (str): Optional code language.
|
|
RETURNS (str): The rendered code block.
|
|
"""
|
|
return "```{}\n{}\n```".format(lang, text)
|
|
|
|
def code(self, text: str) -> str:
|
|
"""Create Markdown inline code.
|
|
|
|
text (str): The inline code text.
|
|
RETURNS (str): The rendered code text.
|
|
"""
|
|
return self._wrap(text, "`")
|
|
|
|
def bold(self, text: str) -> str:
|
|
"""Create bold text.
|
|
|
|
text (str): The text to format in boldface.
|
|
RETURNS (str): The formatted text.
|
|
"""
|
|
return self._wrap(text, "**")
|
|
|
|
def italic(self, text: str):
|
|
"""Create italic text.
|
|
|
|
text (str): The text to italicize.
|
|
RETURNS (str): The formatted text.
|
|
"""
|
|
return self._wrap(text, "_")
|
|
|
|
def _wrap(self, text, marker):
|
|
return "{}{}{}".format(marker, text, marker)
|