Coverage for src / pyTRLCConverter / marko / md2rst_renderer.py: 74%
81 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 12:20 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 12:20 +0000
1"""reStructuredText renderer for Marko.
2 It is used to convert CommonMark AST to reStructuredText format.
4 Author: Andreas Merkle (andreas.merkle@newtec.de)
5"""
7# pyTRLCConverter - A tool to convert TRLC files to specific formats.
8# Copyright (c) 2024 - 2026 NewTec GmbH
9#
10# This file is part of pyTRLCConverter program.
11#
12# The pyTRLCConverter program is free software: you can redistribute it and/or modify it under
13# the terms of the GNU General Public License as published by the Free Software Foundation,
14# either version 3 of the License, or (at your option) any later version.
15#
16# The pyTRLCConverter program is distributed in the hope that it will be useful, but
17# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License along with pyTRLCConverter.
21# If not, see <https://www.gnu.org/licenses/>.
23# Imports **********************************************************************
25from __future__ import annotations
26from typing import TYPE_CHECKING, Any, cast
27from marko import Renderer
29if TYPE_CHECKING:
30 from . import block, inline
32# Variables ********************************************************************
34# Classes **********************************************************************
36# pylint: disable-next=too-many-public-methods
37class Md2RstRenderer(Renderer):
38 # lobster-trace: SwRequirements.sw_req_rst_render_md
39 """
40 Renderer for reStructuredText output.
41 It is used to convert CommonMark Markdown to reStructuredText format.
42 """
44 def __init__(self) -> None:
45 """
46 Initializes the renderer.
47 """
48 super().__init__()
49 self._list_indent_level = 0
51 def render_paragraph(self, element: block.Paragraph) -> str:
52 """
53 Renders a paragraph element.
55 Args:
56 element (block.Paragraph): The paragraph element to render.
58 Returns:
59 str: The rendered paragraph as a string.
60 """
61 return self.render_children(element) + "\n\n"
63 def render_list(self, element: block.List) -> str:
64 """
65 Renders a list (ordered or unordered) element.
67 Args:
68 element (block.List): The list element to render.
70 Returns:
71 str: The rendered list as a string.
72 """
73 items = []
75 self._list_indent_level += 1
77 for index, child in enumerate(element.children):
78 marker = f"{index + 1}." if element.ordered else "-"
79 item = self.render_list_item(child, marker)
80 items.append(item)
82 self._list_indent_level -= 1
84 return "\n".join(items) + "\n"
86 def render_list_item(self, element: block.ListItem, marker="-") -> str:
87 """
88 Renders a list item element.
90 Args:
91 element (block.ListItem): The list item element to render.
92 marker (str, optional): The marker to use for the list item. Defaults to "*".
94 Returns:
95 str: The rendered list item as a string.
96 """
97 indent = 2
98 content = self.render_children(element)
100 return f"{' ' * indent * (self._list_indent_level - 1)}{marker} {content}"
102 def render_quote(self, element: block.Quote) -> str:
103 """
104 Renders a blockquote element.
106 Args:
107 element (block.Quote): The blockquote element to render.
109 Returns:
110 str: The rendered blockquote as a string.
111 """
112 quote = self.render_children(element)
113 quoted = "\n".join([f" {line}" if line.strip() else "" for line in quote.splitlines()])
115 return quoted + "\n\n"
117 def render_fenced_code(self, element: block.FencedCode) -> str:
118 """
119 Renders a fenced code block element.
121 Args:
122 element (block.FencedCode): The fenced code block element to render.
124 Returns:
125 str: The rendered fenced code block as a string.
126 """
127 lang = element.lang or ""
128 code = element.children[0].children # type: ignore
130 return f".. code-block:: {lang}\n\n " + "\n ".join(code.splitlines()) + "\n\n"
132 def render_code_block(self, element: block.CodeBlock) -> str:
133 """
134 Renders a code block element.
136 Args:
137 element (block.CodeBlock): The code block element to render.
139 Returns:
140 str: The rendered code block as a string.
141 """
142 return self.render_fenced_code(cast("block.FencedCode", element))
144 def render_html_block(self, element: block.HTMLBlock) -> str:
145 """
146 Renders a raw HTML block element.
148 Args:
149 element (block.HTMLBlock): The HTML block element to render.
151 Returns:
152 str: The rendered HTML block as a string.
153 """
154 # reStructuredText does not support raw HTML, so output as a literal block.
155 body = element.body
157 return "::\n\n " + "\n ".join(body.splitlines()) + "\n\n"
159 # pylint: disable-next=unused-argument
160 def render_thematic_break(self, element: block.ThematicBreak) -> str:
161 """
162 Renders a thematic break (horizontal rule) element.
164 Args:
165 element (block.ThematicBreak): The thematic break element to render.
167 Returns:
168 str: The rendered thematic break as a string.
169 """
170 return "\n----\n\n"
172 def render_heading(self, element: block.Heading) -> str:
173 """
174 Renders a heading element.
176 Args:
177 element (block.Heading): The heading element to render.
179 Returns:
180 str: The rendered heading as a string.
181 """
182 text = self.render_children(element)
183 underline = {
184 1: "=",
185 2: "-",
186 3: "~",
187 4: "^",
188 5: '"',
189 6: "'"
190 }.get(element.level, "-")
192 return f"{text}\n{underline * len(text)}\n\n"
194 def render_setext_heading(self, element: block.SetextHeading) -> str:
195 """
196 Renders a setext heading element.
198 Args:
199 element (block.SetextHeading): The setext heading element to render.
201 Returns:
202 str: The rendered setext heading as a string.
203 """
204 return self.render_heading(cast("block.Heading", element))
206 # pylint: disable-next=unused-argument
207 def render_blank_line(self, element: block.BlankLine) -> str:
208 """
209 Renders a blank line element.
211 Args:
212 element (block.BlankLine): The blank line element to render.
214 Returns:
215 str: The rendered blank line as a string.
216 """
217 return "\n"
219 # pylint: disable-next=unused-argument
220 def render_link_ref_def(self, element: block.LinkRefDef) -> str:
221 """
222 Renders a link reference definition element.
224 Args:
225 element (block.LinkRefDef): The link reference definition element to render.
227 Returns:
228 str: The rendered link reference definition as a string.
229 """
230 # reStructuredText uses reference links differently than Markdown.
231 # It shall not be rendered in the document.
232 return ""
234 def render_emphasis(self, element: inline.Emphasis) -> str:
235 """
236 Renders an emphasis (italic) element.
238 Args:
239 element (inline.Emphasis): The emphasis element to render.
241 Returns:
242 str: The rendered emphasis as a string.
243 """
244 return f"*{self.render_children(element)}*"
246 def render_strong_emphasis(self, element: inline.StrongEmphasis) -> str:
247 """
248 Renders a strong emphasis (bold) element.
250 Args:
251 element (inline.StrongEmphasis): The strong emphasis element to render.
253 Returns:
254 str: The rendered strong emphasis as a string.
255 """
256 return f"**{self.render_children(element)}**"
258 def render_inline_html(self, element: inline.InlineHTML) -> str:
259 """
260 Renders an inline HTML element.
262 Args:
263 element (inline.InlineHTML): The inline HTML element to render.
265 Returns:
266 str: The rendered inline HTML as a string.
267 """
268 # Output as literal block.
269 html_content = cast(str, element.children)
270 return f"``{html_content}``"
272 def render_plain_text(self, element: Any) -> str:
273 """
274 Renders plain text or any element with string children.
276 Args:
277 element (Any): The element to render.
279 Returns:
280 str: The rendered plain text as a string.
281 """
282 if isinstance(element.children, str):
283 return element.children
285 return self.render_children(element)
287 def render_link(self, element: inline.Link) -> str:
288 """
289 Renders a link element.
291 Args:
292 element (inline.Link): The link element to render.
294 Returns:
295 str: The rendered link as a string.
296 """
297 body = self.render_children(element)
298 url = element.dest
299 title = f" ({element.title})" if element.title else ""
301 return f"`{body} <{url}>`_{title}"
303 def render_auto_link(self, element: inline.AutoLink) -> str:
304 """
305 Renders an auto link element.
307 Args:
308 element (inline.AutoLink): The auto link element to render.
310 Returns:
311 str: The rendered auto link as a string.
312 """
313 return self.render_link(cast("inline.Link", element))
315 def render_url(self, element: Any) -> str:
316 """Renders a GitHub Flavored Markdown URL element.
318 Args:
319 element (Any): The URL element.
321 Returns:
322 str: The rendered URL as an RST link.
323 """
324 return self.render_link(cast("inline.Link", element))
326 def render_image(self, element: inline.Image) -> str:
327 """
328 Renders an image element.
330 Args:
331 element (inline.Image): The image element to render.
333 Returns:
334 str: The rendered image as a string.
335 """
336 url = element.dest
337 alt = self.render_children(element)
338 title = f" :alt: {alt}" if alt else ""
339 extra_title = f" :title: {element.title}" if element.title else ""
341 return f".. image:: {url}\n{title}\n{extra_title}\n"
343 def render_literal(self, element: inline.Literal) -> str:
344 """
345 Renders a literal (inline code) element.
347 Args:
348 element (inline.Literal): The literal element to render.
350 Returns:
351 str: The rendered literal as a string.
352 """
353 return self.render_raw_text(cast("inline.RawText", element))
355 def render_raw_text(self, element: inline.RawText) -> str:
356 """
357 Renders a raw text element.
359 Args:
360 element (inline.RawText): The raw text element to render.
362 Returns:
363 str: The rendered raw text as a string.
364 """
365 return f"{element.children}"
367 # pylint: disable-next=unused-argument
368 def render_line_break(self, element: inline.LineBreak) -> str:
369 """
370 Renders a line break element.
372 Args:
373 element (inline.LineBreak): The line break element to render.
375 Returns:
376 str: The rendered line break as a string.
377 """
378 return "\n"
380 def render_code_span(self, element: inline.CodeSpan) -> str:
381 """
382 Renders a code span (inline code) element.
384 Args:
385 element (inline.CodeSpan): The code span element to render.
387 Returns:
388 str: The rendered code span as a string.
389 """
390 return f"``{cast(str, element.children)}``"
392# Functions ********************************************************************
394# Main *************************************************************************