Skip to content

Commit 064bb13

Browse files
committed
refactor: extract renderer classes to separate files
Split the renderer module into focused single-responsibility files for better maintainability and clearer code organization. Changes: - Extract `MDRender` class to `renderer/md_render.py` - Extract `RenderNode` class to `renderer/render_node.py` - Extract `render_with_features()` to `renderer/dispatch.py` - Simplify `renderer/__init__.py` to re-export public API
1 parent 47626c2 commit 064bb13

File tree

5 files changed

+247
-243
lines changed

5 files changed

+247
-243
lines changed

md2zhihu/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
from .parser import FrontMatter
2525
from .parser import ParserConfig
2626
from .parser import load_external_refs
27-
from .renderer import MDRender
2827
from .renderer import RenderNode
28+
from .renderer.md_render import MDRender
2929
from .utils import add_paragraph_end
3030
from .utils import asset_fn
3131
from .utils import escape

md2zhihu/renderer/__init__.py

Lines changed: 2 additions & 242 deletions
Original file line numberDiff line numberDiff line change
@@ -1,244 +1,4 @@
11
"""Renderer classes for md2zhihu"""
22

3-
import pprint
4-
from typing import List
5-
from typing import Optional
6-
7-
from ..utils import add_paragraph_end
8-
from ..utils import indent
9-
from ..utils import msg
10-
from ..utils import strip_paragraph_end
11-
12-
13-
class MDRender(object):
14-
# platform specific renderer
15-
16-
def __init__(self, conf, features: dict):
17-
self.conf = conf
18-
self.features = features
19-
20-
def render_node(self, rnode):
21-
"""
22-
Render a AST node into lines of text
23-
24-
Args:
25-
rnode is a RenderNode instance
26-
"""
27-
28-
n = rnode.node
29-
typ = n["type"]
30-
31-
# customized renderers:
32-
33-
lines = render_with_features(self, rnode, self.features)
34-
if lines is not None:
35-
return lines
36-
else:
37-
# can not render, continue with default handler
38-
pass
39-
40-
# default renderers:
41-
42-
if typ == "thematic_break":
43-
return ["---", ""]
44-
45-
if typ == "paragraph":
46-
lines = self.render(rnode)
47-
return "".join(lines).split("\n") + [""]
48-
49-
if typ == "text":
50-
return [n["text"]]
51-
52-
if typ == "strong":
53-
lines = self.render(rnode)
54-
lines[0] = "**" + lines[0]
55-
lines[-1] = lines[-1] + "**"
56-
return lines
57-
58-
if typ == "math_block":
59-
return ["$$", n["text"], "$$"]
60-
61-
if typ == "math_inline":
62-
return ["$$ " + n["text"].strip() + " $$"]
63-
64-
if typ == "table":
65-
return self.render(rnode) + [""]
66-
67-
if typ == "table_head":
68-
alignmap = {
69-
"left": ":--",
70-
"right": "--:",
71-
"center": ":-:",
72-
None: "---",
73-
}
74-
lines = self.render(rnode)
75-
aligns = [alignmap[x["align"]] for x in n["children"]]
76-
aligns = "| " + " | ".join(aligns) + " |"
77-
return ["| " + " | ".join(lines) + " |", aligns]
78-
79-
if typ == "table_cell":
80-
lines = self.render(rnode)
81-
return ["".join(lines)]
82-
83-
if typ == "table_body":
84-
return self.render(rnode)
85-
86-
if typ == "table_row":
87-
lines = self.render(rnode)
88-
return ["| " + " | ".join(lines) + " |"]
89-
90-
if typ == "block_code":
91-
# remove the last \n
92-
return ["```" + (n["info"] or "")] + n["text"][:-1].split("\n") + ["```", ""]
93-
94-
if typ == "codespan":
95-
return [("`" + n["text"] + "`")]
96-
97-
if typ == "image":
98-
if n["title"] is None:
99-
return ["![{alt}]({src})".format(**n)]
100-
else:
101-
return ["![{alt}]({src} {title})".format(**n)]
102-
103-
if typ == "list":
104-
lines = self.render(rnode)
105-
return add_paragraph_end(lines)
106-
107-
if typ == "list_item":
108-
lines = self.render(rnode)
109-
110-
# parent is a `list` node
111-
parent = rnode.parent
112-
assert parent.node["type"] == "list"
113-
114-
head = "- "
115-
if parent.node["ordered"]:
116-
head = "1. "
117-
118-
lines[0] = head + lines[0]
119-
lines = lines[0:1] + [indent(x) for x in lines[1:]]
120-
return lines
121-
122-
if typ == "block_text":
123-
lines = self.render(rnode)
124-
return "".join(lines).split("\n")
125-
126-
if typ == "block_quote":
127-
lines = self.render(rnode)
128-
lines = strip_paragraph_end(lines)
129-
lines = ["> " + x for x in lines]
130-
return lines + [""]
131-
132-
if typ == "newline":
133-
return [""]
134-
135-
if typ == "block_html":
136-
return add_paragraph_end([n["text"]])
137-
138-
if typ == "link":
139-
# TODO title
140-
lines = self.render(rnode)
141-
lines[0] = "[" + lines[0]
142-
lines[-1] = lines[-1] + "](" + n["link"] + ")"
143-
144-
return lines
145-
146-
if typ == "heading":
147-
lines = self.render(rnode)
148-
lines[0] = "#" * n["level"] + " " + lines[0]
149-
return lines + [""]
150-
151-
if typ == "strikethrough":
152-
lines = self.render(rnode)
153-
lines[0] = "~~" + lines[0]
154-
lines[-1] = lines[-1] + "~~"
155-
return lines
156-
157-
if typ == "emphasis":
158-
lines = self.render(rnode)
159-
lines[0] = "*" + lines[0]
160-
lines[-1] = lines[-1] + "*"
161-
return lines
162-
163-
if typ == "inline_html":
164-
return [n["text"]]
165-
166-
if typ == "linebreak":
167-
return [" \n"]
168-
169-
print(typ, n.keys())
170-
pprint.pprint(n)
171-
return ["***:" + typ]
172-
173-
def render(self, rnode) -> List[str]:
174-
rst = []
175-
for n in rnode.node["children"]:
176-
child = rnode.new_child(n)
177-
lines = self.render_node(child)
178-
rst.extend(lines)
179-
180-
return rst
181-
182-
def msg(self, *args):
183-
msg(*args)
184-
185-
186-
class RenderNode(object):
187-
"""
188-
RenderNode is a container of current ast-node and parent
189-
"""
190-
191-
def __init__(self, n, parent=None):
192-
"""
193-
:param n: ast node: a normal dictionary such as {'type': 'text' ... }
194-
:param parent: parent RenderNode
195-
"""
196-
self.node = n
197-
198-
self.level = 0
199-
200-
# parent RenderNode
201-
self.parent = parent
202-
203-
def new_child(self, n):
204-
c = RenderNode(n, parent=self)
205-
c.level = self.level + 1
206-
return c
207-
208-
def to_str(self):
209-
t = "{}".format(self.node.get("type"))
210-
if self.parent is None:
211-
return t
212-
213-
return self.parent.to_str() + " -> " + t
214-
215-
216-
# features: {typ:action(), typ2:{subtyp:action()}}
217-
def render_with_features(mdrender, rnode: RenderNode, features=None) -> Optional[List[str]]:
218-
if features is None:
219-
features = {}
220-
221-
n = rnode.node
222-
223-
node_type = n["type"]
224-
225-
if node_type not in features:
226-
if "*" in features:
227-
return features["*"](mdrender, rnode)
228-
else:
229-
return None
230-
231-
type_handler = features[node_type]
232-
if callable(type_handler):
233-
return type_handler(mdrender, rnode)
234-
235-
# subtype is info, the type after "```"
236-
lang = n["info"] or ""
237-
238-
if lang in type_handler:
239-
return type_handler[lang](mdrender, rnode)
240-
241-
if "*" in type_handler:
242-
return type_handler["*"](mdrender, rnode)
243-
244-
return None
3+
from .md_render import MDRender
4+
from .render_node import RenderNode

md2zhihu/renderer/dispatch.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from typing import List
2+
from typing import Optional
3+
4+
from .render_node import RenderNode
5+
6+
7+
# features: {typ:action(), typ2:{subtyp:action()}}
8+
def render_with_features(mdrender, rnode: RenderNode, features=None) -> Optional[List[str]]:
9+
if features is None:
10+
features = {}
11+
12+
n = rnode.node
13+
14+
node_type = n["type"]
15+
16+
if node_type not in features:
17+
if "*" in features:
18+
return features["*"](mdrender, rnode)
19+
else:
20+
return None
21+
22+
type_handler = features[node_type]
23+
if callable(type_handler):
24+
return type_handler(mdrender, rnode)
25+
26+
# subtype is info, the type after "```"
27+
lang = n["info"] or ""
28+
29+
if lang in type_handler:
30+
return type_handler[lang](mdrender, rnode)
31+
32+
if "*" in type_handler:
33+
return type_handler["*"](mdrender, rnode)
34+
35+
return None

0 commit comments

Comments
 (0)