""" ctstruct/renderers/markdown.py CTTree → Markdown 렌더러 """ from __future__ import annotations from ..node import CTNode from ..tree import CTTree from ..types import NodeType from .base import BaseRenderer class MarkdownRenderer(BaseRenderer): """CTTree 를 Markdown 텍스트로 렌더링.""" def render(self, tree: CTTree) -> str: lines: list[str] = [] # frontmatter meta = tree.root.attr("metadata") if meta: lines.append("---") for k, v in meta.items(): lines.append(f"{k}: {v}") lines.append("---") lines.append("") for child in tree.root: lines.extend(self._render_node(child)) lines.append("") return "\n".join(lines).rstrip() + "\n" def _render_node(self, node: CTNode) -> list[str]: t = node.node_type if t == NodeType.SECTION: out: list[str] = [] for c in node: out.extend(self._render_node(c)) out.append("") return out if t == NodeType.HEADING: level = node.attr("level", 1) return [f"{'#' * level} {node.content or ''}"] if t == NodeType.PARAGRAPH: return [node.content or ""] if t == NodeType.CODE_BLOCK: lang = node.attr("language", "") return [f"```{lang}", node.content or "", "```"] if t == NodeType.BLOCKQUOTE: return [f"> {line}" for line in (node.content or "").splitlines()] if t == NodeType.DIVIDER: return ["---"] if t == NodeType.LIST: out = [] style = node.attr("style", "unordered") for idx, item in enumerate(node, 1): prefix = f"{idx}." if style == "ordered" else "-" out.append(f"{prefix} {item.content or ''}") return out if t == NodeType.TABLE: return self._render_table(node) if t == NodeType.METADATA: return [] # 메타 노드는 frontmatter로 처리됨 # 그 외 노드는 텍스트 추출 return [node.text_content()] def _render_table(self, table: CTNode) -> list[str]: rows = list(table) if not rows: return [] out: list[str] = [] for i, row in enumerate(rows): cells = [c.content or "" for c in row] out.append("| " + " | ".join(cells) + " |") if i == 0: out.append("| " + " | ".join(["---"] * len(cells)) + " |") return out