From 90b0313470b29ea3335574fe901476d20a548fa1 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 20 Oct 2026 12:32:41 +0200 Subject: [PATCH] fix: render nested profile blocks as lists A key holding a mapping was written as a paragraph, and its children landed eight spaces deep inside the four-space admonition, where Markdown reads an indented code block: the clownmdemu analysis showed raw asterisks. Each level is now a list item with its children as a sublist, booleans read yes or no, and a null reads none. --- scripts/siterender.py | 54 ++++++++++++++++++++------------------ tests/test_site_exports.py | 33 +++++++++++++++++++++++ 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/scripts/siterender.py b/scripts/siterender.py index 1cf06c88..619058a7 100644 --- a/scripts/siterender.py +++ b/scripts/siterender.py @@ -170,55 +170,57 @@ def _system_link(sys_id: str, prefix: str = "") -> str: def _field_text(key: object, value: object) -> str: """A scalar of a structured block. A source reference reads as code.""" - text = str(value) + if isinstance(value, bool): + text = "yes" if value else "no" + elif value is None: + text = "none" + else: + text = str(value) + if len(text) > 200: + text = text[:200] + "..." return f"`{text}`" if key == "source_ref" else text +def _field_label(key: object) -> str: + """A YAML key as the page shows it: words, not an identifier.""" + return str(key).replace("_", " ") + + def _render_yaml_value(lines: list[str], val: object, indent: int = 4) -> None: - """Render any YAML value as indented markdown.""" + """Render any YAML value as a nested markdown list. + + Every level is a list item and its children a sublist four spaces in. + A key written as a paragraph with its children indented below it read as + an indented code block: the clownmdemu page showed the raw asterisks. + """ pad = " " * indent if isinstance(val, dict): for k, v in val.items(): - if isinstance(v, dict): - lines.append(f"{pad}**{k}:**") + if isinstance(v, (dict, list)) and v: + lines.append(f"{pad}- {_field_label(k)}:") lines.append("") _render_yaml_value(lines, v, indent + 4) - elif isinstance(v, list): - lines.append(f"{pad}**{k}:**") - lines.append("") - for item in v: - if isinstance(item, dict): - parts = [ - f"{ik}: {_field_text(ik, iv)}" - for ik, iv in item.items() - if not isinstance(iv, (dict, list)) - ] - lines.append(f"{pad}- {', '.join(parts)}") - else: - lines.append(f"{pad}- {item}") - lines.append("") else: - # Truncate very long strings in tables - sv = str(v) - if len(sv) > 200: - sv = sv[:200] + "..." - lines.append(f"{pad}- **{k}:** {_field_text(k, sv)}") + lines.append(f"{pad}- {_field_label(k)}: {_field_text(k, v)}") + lines.append("") elif isinstance(val, list): for item in val: if isinstance(item, dict): parts = [ - f"{ik}: {_field_text(ik, iv)}" + f"{_field_label(ik)}: {_field_text(ik, iv)}" for ik, iv in item.items() if not isinstance(iv, (dict, list)) ] lines.append(f"{pad}- {', '.join(parts)}") else: - lines.append(f"{pad}- {item}") + lines.append(f"{pad}- {_field_text(None, item)}") + lines.append("") elif isinstance(val, str) and "\n" in val: for line in val.split("\n"): lines.append(f"{pad}{line}") else: - lines.append(f"{pad}{val}") + lines.append(f"{pad}{_field_text(None, val)}") + def _platform_link(name: str, display: str, prefix: str = "") -> str: """Generate a markdown link to a platform page.""" diff --git a/tests/test_site_exports.py b/tests/test_site_exports.py index dd5e4bab..441fefc2 100644 --- a/tests/test_site_exports.py +++ b/tests/test_site_exports.py @@ -835,5 +835,38 @@ class AnEmulatorPageOffersItsInstall(unittest.TestCase): self.assertIn('"--emulator"', source) +class NestedProfileBlocksRenderAsLists(unittest.TestCase): + """The clownmdemu page showed "- **external_required:** False" in a code + block: a key written as a paragraph left its children indented eight + spaces inside the admonition, which Markdown reads as code.""" + + def _html(self, value: object) -> str: + try: + import markdown + import pymdownx.details # noqa: F401 + except ImportError: + self.skipTest("markdown and pymdown-extensions are not installed") + from siterender import _render_yaml_value + + lines = ['???+ abstract "Analysis"', ""] + _render_yaml_value(lines, value, 4) + return markdown.markdown( + "\n".join(lines), extensions=["admonition", "pymdownx.details"] + ) + + def test_a_nested_mapping_is_a_nested_list(self): + html = self._html({"megacd_bios": {"external_required": False, "source_ref": "a.c:1"}, + "tmss": {"supported": True}}) + self.assertNotIn("
", html)
+ self.assertNotIn("**", html)
+ self.assertIn("external required: no", html)
+ self.assertIn("a.c:1", html)
+
+ def test_a_list_under_a_key_is_a_sublist(self):
+ html = self._html({"roms": [{"name": "a.bin", "size": 2}, "b.bin"]})
+ self.assertNotIn("", html)
+ self.assertIn("name: a.bin, size: 2", html)
+
+
if __name__ == "__main__":
unittest.main()