Skip to content

Commit 44350c4

Browse files
committed
add transformer module
1 parent 0c5f00a commit 44350c4

6 files changed

Lines changed: 688 additions & 313 deletions

File tree

packages/reflex-docgen/src/reflex_docgen/markdown/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Markdown parsing and types for Reflex documentation."""
22

3+
from reflex_docgen.markdown import transformer as transformer
34
from reflex_docgen.markdown._parser import parse_document as parse_document
45
from reflex_docgen.markdown._types import Block as Block
56
from reflex_docgen.markdown._types import BoldSpan as BoldSpan
@@ -52,4 +53,5 @@
5253
"TextSpan",
5354
"ThematicBreakBlock",
5455
"parse_document",
56+
"transformer",
5557
]

packages/reflex-docgen/src/reflex_docgen/markdown/_types.py

Lines changed: 0 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import re
65
from dataclasses import dataclass
76

87
# ---------------------------------------------------------------------------
@@ -20,14 +19,6 @@ class TextSpan:
2019

2120
text: str
2221

23-
def as_markdown(self) -> str:
24-
"""Render back to markdown.
25-
26-
Returns:
27-
A markdown string.
28-
"""
29-
return self.text
30-
3122

3223
@dataclass(frozen=True, slots=True, kw_only=True)
3324
class BoldSpan:
@@ -39,15 +30,6 @@ class BoldSpan:
3930

4031
children: tuple[Span, ...]
4132

42-
def as_markdown(self) -> str:
43-
"""Render back to markdown.
44-
45-
Returns:
46-
A markdown string.
47-
"""
48-
inner = "".join(c.as_markdown() for c in self.children)
49-
return f"**{inner}**"
50-
5133

5234
@dataclass(frozen=True, slots=True, kw_only=True)
5335
class ItalicSpan:
@@ -59,15 +41,6 @@ class ItalicSpan:
5941

6042
children: tuple[Span, ...]
6143

62-
def as_markdown(self) -> str:
63-
"""Render back to markdown.
64-
65-
Returns:
66-
A markdown string.
67-
"""
68-
inner = "".join(c.as_markdown() for c in self.children)
69-
return f"*{inner}*"
70-
7144

7245
@dataclass(frozen=True, slots=True, kw_only=True)
7346
class StrikethroughSpan:
@@ -79,15 +52,6 @@ class StrikethroughSpan:
7952

8053
children: tuple[Span, ...]
8154

82-
def as_markdown(self) -> str:
83-
"""Render back to markdown.
84-
85-
Returns:
86-
A markdown string.
87-
"""
88-
inner = "".join(c.as_markdown() for c in self.children)
89-
return f"~~{inner}~~"
90-
9155

9256
@dataclass(frozen=True, slots=True, kw_only=True)
9357
class CodeSpan:
@@ -99,14 +63,6 @@ class CodeSpan:
9963

10064
code: str
10165

102-
def as_markdown(self) -> str:
103-
"""Render back to markdown.
104-
105-
Returns:
106-
A markdown string.
107-
"""
108-
return f"`{self.code}`"
109-
11066

11167
@dataclass(frozen=True, slots=True, kw_only=True)
11268
class LinkSpan:
@@ -120,15 +76,6 @@ class LinkSpan:
12076
children: tuple[Span, ...]
12177
target: str
12278

123-
def as_markdown(self) -> str:
124-
"""Render back to markdown.
125-
126-
Returns:
127-
A markdown string.
128-
"""
129-
inner = "".join(c.as_markdown() for c in self.children)
130-
return f"[{inner}]({self.target})"
131-
13279

13380
@dataclass(frozen=True, slots=True, kw_only=True)
13481
class ImageSpan:
@@ -142,15 +89,6 @@ class ImageSpan:
14289
children: tuple[Span, ...]
14390
src: str
14491

145-
def as_markdown(self) -> str:
146-
"""Render back to markdown.
147-
148-
Returns:
149-
A markdown string.
150-
"""
151-
inner = "".join(c.as_markdown() for c in self.children)
152-
return f"![{inner}]({self.src})"
153-
15492

15593
@dataclass(frozen=True, slots=True, kw_only=True)
15694
class LineBreakSpan:
@@ -162,14 +100,6 @@ class LineBreakSpan:
162100

163101
soft: bool
164102

165-
def as_markdown(self) -> str:
166-
"""Render back to markdown.
167-
168-
Returns:
169-
A markdown string.
170-
"""
171-
return "\n" if self.soft else " \n"
172-
173103

174104
#: Union of all inline span types.
175105
Span = (
@@ -184,36 +114,6 @@ def as_markdown(self) -> str:
184114
)
185115

186116

187-
def _spans_as_markdown(spans: tuple[Span, ...]) -> str:
188-
"""Render a sequence of spans back to markdown.
189-
190-
Args:
191-
spans: The inline spans to render.
192-
193-
Returns:
194-
A markdown string.
195-
"""
196-
return "".join(s.as_markdown() for s in spans)
197-
198-
199-
_BACKTICK_FENCE_RE = re.compile(r"^(`{3,})", re.MULTILINE)
200-
201-
202-
def _fence_for(content: str) -> str:
203-
"""Return a backtick fence long enough to wrap *content* safely.
204-
205-
Args:
206-
content: The code block content that may contain backtick fences.
207-
208-
Returns:
209-
A backtick fence string (at least 3 backticks).
210-
"""
211-
max_run = 3
212-
for m in _BACKTICK_FENCE_RE.finditer(content):
213-
max_run = max(max_run, len(m.group(1)) + 1)
214-
return "`" * max_run
215-
216-
217117
# ---------------------------------------------------------------------------
218118
# Block types
219119
# ---------------------------------------------------------------------------
@@ -248,25 +148,6 @@ class FrontMatter:
248148
title: str | None
249149
component_previews: tuple[ComponentPreview, ...]
250150

251-
def as_markdown(self) -> str:
252-
"""Render back to markdown.
253-
254-
Returns:
255-
A markdown string.
256-
"""
257-
import yaml
258-
259-
data: dict[str, object] = {}
260-
if self.components:
261-
data["components"] = list(self.components)
262-
if self.only_low_level:
263-
data["only_low_level"] = [True]
264-
if self.title is not None:
265-
data["title"] = self.title
266-
for preview in self.component_previews:
267-
data[preview.name] = preview.source
268-
return f"---\n{yaml.dump(data, default_flow_style=False, sort_keys=False).rstrip()}\n---"
269-
270151

271152
@dataclass(frozen=True, slots=True, kw_only=True)
272153
class CodeBlock:
@@ -282,18 +163,6 @@ class CodeBlock:
282163
flags: tuple[str, ...]
283164
content: str
284165

285-
def as_markdown(self) -> str:
286-
"""Render back to markdown.
287-
288-
Returns:
289-
A markdown string.
290-
"""
291-
info = self.language or ""
292-
if self.flags:
293-
info = f"{info} {' '.join(self.flags)}" if info else " ".join(self.flags)
294-
fence = _fence_for(self.content)
295-
return f"{fence}{info}\n{self.content}\n{fence}"
296-
297166

298167
@dataclass(frozen=True, slots=True, kw_only=True)
299168
class DirectiveBlock:
@@ -311,16 +180,6 @@ class DirectiveBlock:
311180
args: tuple[str, ...]
312181
content: str
313182

314-
def as_markdown(self) -> str:
315-
"""Render back to markdown.
316-
317-
Returns:
318-
A markdown string.
319-
"""
320-
info_parts = ["md", self.name, *self.args]
321-
fence = _fence_for(self.content)
322-
return f"{fence}{' '.join(info_parts)}\n{self.content}\n{fence}"
323-
324183

325184
@dataclass(frozen=True, slots=True, kw_only=True)
326185
class HeadingBlock:
@@ -334,14 +193,6 @@ class HeadingBlock:
334193
level: int
335194
children: tuple[Span, ...]
336195

337-
def as_markdown(self) -> str:
338-
"""Render back to markdown.
339-
340-
Returns:
341-
A markdown string.
342-
"""
343-
return f"{'#' * self.level} {_spans_as_markdown(self.children)}"
344-
345196

346197
@dataclass(frozen=True, slots=True, kw_only=True)
347198
class TextBlock:
@@ -353,14 +204,6 @@ class TextBlock:
353204

354205
children: tuple[Span, ...]
355206

356-
def as_markdown(self) -> str:
357-
"""Render back to markdown.
358-
359-
Returns:
360-
A markdown string.
361-
"""
362-
return _spans_as_markdown(self.children)
363-
364207

365208
@dataclass(frozen=True, slots=True, kw_only=True)
366209
class ListItem:
@@ -387,22 +230,6 @@ class ListBlock:
387230
start: int | None
388231
items: tuple[ListItem, ...]
389232

390-
def as_markdown(self) -> str:
391-
"""Render back to markdown.
392-
393-
Returns:
394-
A markdown string.
395-
"""
396-
lines: list[str] = []
397-
for i, item in enumerate(self.items):
398-
prefix = f"{(self.start or 1) + i}. " if self.ordered else "- "
399-
item_md = "\n\n".join(child.as_markdown() for child in item.children)
400-
first, *rest = item_md.split("\n")
401-
lines.append(f"{prefix}{first}")
402-
indent = " " * len(prefix)
403-
lines.extend(f"{indent}{line}" if line else "" for line in rest)
404-
return "\n".join(lines)
405-
406233

407234
@dataclass(frozen=True, slots=True, kw_only=True)
408235
class QuoteBlock:
@@ -414,15 +241,6 @@ class QuoteBlock:
414241

415242
children: tuple[Block, ...]
416243

417-
def as_markdown(self) -> str:
418-
"""Render back to markdown.
419-
420-
Returns:
421-
A markdown string.
422-
"""
423-
inner = "\n\n".join(child.as_markdown() for child in self.children)
424-
return "\n".join(f"> {line}" if line else ">" for line in inner.split("\n"))
425-
426244

427245
@dataclass(frozen=True, slots=True, kw_only=True)
428246
class TableCell:
@@ -447,15 +265,6 @@ class TableRow:
447265

448266
cells: tuple[TableCell, ...]
449267

450-
def as_markdown(self) -> str:
451-
"""Render back to markdown.
452-
453-
Returns:
454-
A markdown string.
455-
"""
456-
cells = " | ".join(_spans_as_markdown(cell.children) for cell in self.cells)
457-
return f"| {cells} |"
458-
459268

460269
@dataclass(frozen=True, slots=True, kw_only=True)
461270
class TableBlock:
@@ -469,40 +278,11 @@ class TableBlock:
469278
header: TableRow
470279
rows: tuple[TableRow, ...]
471280

472-
def as_markdown(self) -> str:
473-
"""Render back to markdown.
474-
475-
Returns:
476-
A markdown string.
477-
"""
478-
lines = [self.header.as_markdown()]
479-
sep_parts: list[str] = []
480-
for cell in self.header.cells:
481-
if cell.align == "left":
482-
sep_parts.append(":---")
483-
elif cell.align == "right":
484-
sep_parts.append("---:")
485-
elif cell.align == "center":
486-
sep_parts.append(":---:")
487-
else:
488-
sep_parts.append("---")
489-
lines.append(f"| {' | '.join(sep_parts)} |")
490-
lines.extend(row.as_markdown() for row in self.rows)
491-
return "\n".join(lines)
492-
493281

494282
@dataclass(frozen=True, slots=True, kw_only=True)
495283
class ThematicBreakBlock:
496284
"""A thematic break (horizontal rule)."""
497285

498-
def as_markdown(self) -> str:
499-
"""Render back to markdown.
500-
501-
Returns:
502-
A markdown string.
503-
"""
504-
return "---"
505-
506286

507287
#: Union of all block types that can appear in a parsed document.
508288
Block = (
@@ -544,15 +324,3 @@ def code_blocks(self) -> tuple[CodeBlock, ...]:
544324
def directives(self) -> tuple[DirectiveBlock, ...]:
545325
"""Return all directive blocks in the document."""
546326
return tuple(b for b in self.blocks if isinstance(b, DirectiveBlock))
547-
548-
def as_markdown(self) -> str:
549-
"""Render the full document back to markdown.
550-
551-
Returns:
552-
A markdown string.
553-
"""
554-
parts: list[str] = []
555-
if self.frontmatter:
556-
parts.append(self.frontmatter.as_markdown())
557-
parts.extend(block.as_markdown() for block in self.blocks)
558-
return "\n\n".join(parts) + "\n"

0 commit comments

Comments
 (0)