Coverage for moptipy / utils / markdown.py: 89%
70 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""The markdown text format driver."""
3from io import TextIOBase
4from typing import Final
6from moptipy.utils.formatted_string import (
7 NAN,
8 NEGATIVE_INFINITY,
9 NUMBER,
10 POSITIVE_INFINITY,
11 SPECIAL,
12 TEXT,
13)
14from moptipy.utils.latex import SPECIAL_CHARS as __SC
15from moptipy.utils.text_format import MODE_TABLE_HEADER, TextFormatDriver
17#: the special chars
18SPECIAL_CHARS: Final[dict[str, str]] = dict(__SC)
19SPECIAL_CHARS["\u2014"] = "—"
22class Markdown(TextFormatDriver):
23 r"""
24 The markdown text driver.
26 >>> from io import StringIO
27 >>> from moptipy.utils.formatted_string import FormattedStr
28 >>> from moptipy.utils.table import Table
29 >>> s = StringIO()
30 >>> md = Markdown.instance()
31 >>> print(str(md))
32 md
33 >>> with Table(s, "lrc", md) as t:
34 ... with t.header() as header:
35 ... with header.row() as h:
36 ... h.cell(FormattedStr("1", bold=True))
37 ... h.cell(FormattedStr("2", code=True))
38 ... h.cell(FormattedStr("3", italic=True))
39 ... with t.section() as g:
40 ... with g.row() as r:
41 ... r.cell("a")
42 ... r.cell("b")
43 ... r.cell("c")
44 ... with g.row() as r:
45 ... r.cell("d")
46 ... r.cell("e")
47 ... r.cell("f")
48 >>> print(f"'{s.getvalue()}'")
49 '|**1**|`2`|*3*|
50 |:--|--:|:-:|
51 |a|b|c|
52 |d|e|f|
53 '
54 """
56 def end_table_header(self, stream: TextIOBase, cols: str) -> None:
57 """End the header of a markdown table."""
58 for c in cols:
59 if c == "l":
60 stream.write("|:--")
61 elif c == "c":
62 stream.write("|:-:")
63 elif c == "r":
64 stream.write("|--:")
65 else:
66 raise ValueError(f"Invalid col {c!r} in {cols!r}.")
67 stream.write("|\n")
69 def begin_table_row(self, stream: TextIOBase, cols: str,
70 section_index: int, row_index: int,
71 row_mode: int) -> None:
72 """Begin a row in a markdown table."""
73 if (row_mode == MODE_TABLE_HEADER) and (row_index != 0):
74 raise ValueError("pandoc markdown only supports one single header"
75 f" row, but encountered row {row_mode + 1}.")
77 def end_table_row(self, stream: TextIOBase, cols: str,
78 section_index: int, row_index: int) -> None:
79 """End a row in a Markdown table."""
80 stream.write("|\n")
82 def begin_table_cell(self, stream: TextIOBase, cols: str,
83 section_index: int, row_index: int,
84 col_index: int, cell_mode: int) -> None:
85 """Begin a Markdown table cell."""
86 stream.write("|")
88 def text(self, stream: TextIOBase, text: str, bold: bool, italic: bool,
89 code: bool, mode: int) -> None:
90 """Print a text string."""
91 if len(text) <= 0:
92 return
93 if bold:
94 stream.write("**")
95 if italic:
96 stream.write("*")
97 if code:
98 stream.write("`")
100 if mode == TEXT:
101 stream.write(text)
102 elif mode == NUMBER:
103 i: int = text.find("e")
104 if i < 0:
105 i = text.find("E")
106 if i > 0:
107 stream.write(f"{text[:i]}\\*10^{text[i + 1:]}^") # \u00D7
108 else:
109 stream.write(text)
110 elif mode == NAN:
111 stream.write(r"$\emptyset$") # \u2205
112 elif mode == POSITIVE_INFINITY:
113 stream.write(r"$\infty$") # \u221E
114 elif mode == NEGATIVE_INFINITY:
115 stream.write(r"$-\infty$") # -\u221E
116 elif mode == SPECIAL:
117 s: Final[str] = str(text)
118 if s not in SPECIAL_CHARS:
119 raise ValueError(f"invalid special character: {s!r}")
120 stream.write(SPECIAL_CHARS[s])
121 else:
122 raise ValueError(f"invalid mode {mode} for text {text!r}.")
124 if code:
125 stream.write("`")
126 if italic:
127 stream.write("*")
128 if bold:
129 stream.write("**")
131 def __str__(self):
132 """
133 Get the appropriate file suffix.
135 :returns: the file suffix
136 :retval 'md': always
137 """
138 return "md"
140 @staticmethod
141 def instance() -> "Markdown":
142 """
143 Get the markdown format singleton instance.
145 :returns: the singleton instance of the Markdown format
146 """
147 attr: Final[str] = "_instance"
148 func: Final = Markdown.instance
149 if not hasattr(func, attr):
150 setattr(func, attr, Markdown())
151 return getattr(func, attr)