dataops-sdk 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dataops-sdk might be problematic. Click here for more details.
- dataops_sdk/__init__.py +84 -0
- dataops_sdk/constants.py +6 -0
- dataops_sdk/exceptions.py +31 -0
- dataops_sdk/runner/__init__.py +46 -0
- dataops_sdk/runner/datasource_factory.py +83 -0
- dataops_sdk/runner/schemas/nacos/__init__.py +5 -0
- dataops_sdk/runner/schemas/nacos/config.py +40 -0
- dataops_sdk/runner/schemas/nacos/constants.py +235 -0
- dataops_sdk/runner/schemas/nacos/instance.py +103 -0
- dataops_sdk/runner/schemas/nacos/nacos_exception.py +44 -0
- dataops_sdk/runner/schemas/nacos/naming_param.py +62 -0
- dataops_sdk/runner/schemas/nacos/naming_request.py +84 -0
- dataops_sdk/runner/schemas/nacos/naming_response.py +50 -0
- dataops_sdk/runner/schemas/nacos/preserved_metadata_key.py +10 -0
- dataops_sdk/runner/schemas/nacos/service.py +119 -0
- dataops_sdk/runner/utils/__init__.py +42 -0
- dataops_sdk/runner/utils/common.py +209 -0
- dataops_sdk/runner/utils/dbConn_base.py +256 -0
- dataops_sdk/runner/utils/dbConn_sync.py +485 -0
- dataops_sdk/runner/utils/duckdb_manager.py +203 -0
- dataops_sdk/runner/utils/file_transfer_base.py +307 -0
- dataops_sdk/runner/utils/ftp_file_transfer.py +242 -0
- dataops_sdk/runner/utils/log.py +50 -0
- dataops_sdk/runner/utils/nacos.py +96 -0
- dataops_sdk/runner/utils/neo4j_sync.py +236 -0
- dataops_sdk/runner/utils/queue_base.py +224 -0
- dataops_sdk/runner/utils/queue_kafka.py +150 -0
- dataops_sdk/runner/utils/sftp_file_transfer.py +243 -0
- dataops_sdk/schemas/__init__.py +32 -0
- dataops_sdk/schemas/alarm.py +38 -0
- dataops_sdk/schemas/api.py +37 -0
- dataops_sdk/schemas/data_table.py +437 -0
- dataops_sdk/schemas/datasource.py +26 -0
- dataops_sdk/schemas/dict_.py +13 -0
- dataops_sdk/schemas/enums.py +120 -0
- dataops_sdk/schemas/group.py +40 -0
- dataops_sdk/schemas/hazard.py +151 -0
- dataops_sdk/schemas/meta.py +175 -0
- dataops_sdk/schemas/query.py +175 -0
- dataops_sdk/schemas/report.py +22 -0
- dataops_sdk/schemas/skill.py +70 -0
- dataops_sdk/schemas/table.py +65 -0
- dataops_sdk/schemas/user.py +52 -0
- dataops_sdk/schemas/widget.py +651 -0
- dataops_sdk/schemas/workflow.py +63 -0
- dataops_sdk/services/__init__.py +110 -0
- dataops_sdk/services/alarm.py +324 -0
- dataops_sdk/services/api.py +28 -0
- dataops_sdk/services/auth.py +120 -0
- dataops_sdk/services/data_table.py +485 -0
- dataops_sdk/services/dataset.py +127 -0
- dataops_sdk/services/datasource.py +57 -0
- dataops_sdk/services/dict_.py +69 -0
- dataops_sdk/services/hazard.py +260 -0
- dataops_sdk/services/meta.py +523 -0
- dataops_sdk/services/metrics.py +42 -0
- dataops_sdk/services/ref.py +66 -0
- dataops_sdk/services/report.py +36 -0
- dataops_sdk/services/skill.py +276 -0
- dataops_sdk/services/table.py +340 -0
- dataops_sdk/services/widget.py +143 -0
- dataops_sdk/services/workflow.py +102 -0
- dataops_sdk/session.py +292 -0
- dataops_sdk/tiptap/__init__.py +30 -0
- dataops_sdk/tiptap/builder.py +75 -0
- dataops_sdk/tiptap/constants.py +133 -0
- dataops_sdk/tiptap/markdown_parser.py +294 -0
- dataops_sdk/tiptap/models.py +43 -0
- dataops_sdk/tiptap/parse_utils.py +889 -0
- dataops_sdk/tiptap/render_utils.py +846 -0
- dataops_sdk/tiptap/renderers.py +51 -0
- dataops_sdk/tiptap/tool.py +629 -0
- dataops_sdk/utils/__init__.py +15 -0
- dataops_sdk/utils/common.py +211 -0
- dataops_sdk/utils/utils.py +120 -0
- dataops_sdk-0.3.0.dist-info/METADATA +25 -0
- dataops_sdk-0.3.0.dist-info/RECORD +79 -0
- dataops_sdk-0.3.0.dist-info/WHEEL +5 -0
- dataops_sdk-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Renderer interfaces for TipTap nodes and marks.
|
|
2
|
+
|
|
3
|
+
Defines protocols for node renderers and mark renderers that convert
|
|
4
|
+
TipTap JSON nodes to Markdown format.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
# Type alias for node renderer functions
|
|
11
|
+
# Args: tool, node, content, parent_type, index
|
|
12
|
+
NodeRendererFn = Callable[["TipTapTool", dict, str, str | None, int], str]
|
|
13
|
+
|
|
14
|
+
# Type alias for mark renderer (returns tuple of opening and closing strings)
|
|
15
|
+
MarkRendererFn = Callable[[], tuple[str, str]]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NodeRenderer(Protocol):
|
|
19
|
+
"""Protocol for node renderers.
|
|
20
|
+
|
|
21
|
+
Renders a TipTap node to Markdown format.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
tool: TipTapTool instance
|
|
25
|
+
node: The node to render
|
|
26
|
+
content: Rendered child content
|
|
27
|
+
parent_type: Parent node type
|
|
28
|
+
index: Index of this node in parent
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Markdown formatted string
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __call__(
|
|
35
|
+
self,
|
|
36
|
+
tool: "TipTapTool",
|
|
37
|
+
node: dict,
|
|
38
|
+
content: str,
|
|
39
|
+
parent_type: str | None,
|
|
40
|
+
index: int,
|
|
41
|
+
) -> str: ...
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MarkRenderer(Protocol):
|
|
45
|
+
"""Protocol for mark renderers.
|
|
46
|
+
|
|
47
|
+
Provides opening and closing syntax for inline marks.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
opening: str
|
|
51
|
+
closing: str
|
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
"""Core TipTap tool for JSON <-> Markdown conversion."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
from dataops_sdk.tiptap import constants, parse_utils, render_utils
|
|
7
|
+
from dataops_sdk.tiptap.markdown_parser import MarkdownToTipTapParser
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__package__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TipTapTool:
|
|
13
|
+
"""Main class for TipTap JSON <-> Markdown conversion.
|
|
14
|
+
|
|
15
|
+
Supports:
|
|
16
|
+
- serialize(): Convert TipTap JSON to Markdown
|
|
17
|
+
- parse(): Convert Markdown to TipTap JSON
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
indent_style: Indent style (space or tab)
|
|
21
|
+
indent_size: Number of indent characters
|
|
22
|
+
table_style: Table rendering style (markdown or html)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
indent_style: str = constants.Style.INDENT_STYLE_SPACE,
|
|
28
|
+
indent_size: int = constants.Style.DEFAULT_INDENT_SIZE,
|
|
29
|
+
table_style: str = constants.Style.DEFAULT_TABLE_STYLE,
|
|
30
|
+
) -> None:
|
|
31
|
+
self.indent_style = indent_style
|
|
32
|
+
self.indent_size = indent_size
|
|
33
|
+
self.table_style = table_style
|
|
34
|
+
self._node_renderers: dict[str, Callable] = {}
|
|
35
|
+
self._mark_renderers: dict[str, tuple[str, str]] = {}
|
|
36
|
+
self._register_default_renderers()
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def indent_character(self) -> str:
|
|
40
|
+
"""Get the indent character."""
|
|
41
|
+
return "\t" if self.indent_style == constants.IndentStyle.TAB else " "
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def indent_string(self) -> str:
|
|
45
|
+
"""Get the indent string."""
|
|
46
|
+
return self.indent_character * self.indent_size
|
|
47
|
+
|
|
48
|
+
# ==========================================================================
|
|
49
|
+
# Renderer Registration
|
|
50
|
+
# ==========================================================================
|
|
51
|
+
|
|
52
|
+
def register_node_renderer(
|
|
53
|
+
self,
|
|
54
|
+
node_type: str,
|
|
55
|
+
renderer: Callable,
|
|
56
|
+
) -> "TipTapTool":
|
|
57
|
+
"""Register a node renderer.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
node_type: Node type to register
|
|
61
|
+
renderer: Renderer function
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
self for chaining
|
|
65
|
+
"""
|
|
66
|
+
self._node_renderers[node_type] = renderer
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def register_mark_renderer(
|
|
70
|
+
self,
|
|
71
|
+
mark_type: str,
|
|
72
|
+
opening: str,
|
|
73
|
+
closing: str,
|
|
74
|
+
) -> "TipTapTool":
|
|
75
|
+
"""Register a mark renderer.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
mark_type: Mark type to register
|
|
79
|
+
opening: Opening syntax
|
|
80
|
+
closing: Closing syntax
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
self for chaining
|
|
84
|
+
"""
|
|
85
|
+
self._mark_renderers[mark_type] = (opening, closing)
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def get_node_renderer(self, node_type: str) -> Callable | None:
|
|
89
|
+
"""Get a registered node renderer.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
node_type: Node type
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Renderer function or None
|
|
96
|
+
"""
|
|
97
|
+
return self._node_renderers.get(node_type)
|
|
98
|
+
|
|
99
|
+
def get_mark_renderer(self, mark_type: str) -> tuple[str, str] | None:
|
|
100
|
+
"""Get a registered mark renderer.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
mark_type: Mark type
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Tuple of (opening, closing) or None
|
|
107
|
+
"""
|
|
108
|
+
return self._mark_renderers.get(mark_type)
|
|
109
|
+
|
|
110
|
+
# ==========================================================================
|
|
111
|
+
# Serialization (TipTap JSON -> Markdown)
|
|
112
|
+
# ==========================================================================
|
|
113
|
+
|
|
114
|
+
def serialize(self, doc: dict) -> str:
|
|
115
|
+
"""Serialize TipTap JSON document to Markdown.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
doc: TipTap JSON document (dict with 'type' and 'content')
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Markdown formatted string
|
|
122
|
+
"""
|
|
123
|
+
if doc is None:
|
|
124
|
+
return ""
|
|
125
|
+
|
|
126
|
+
doc_type = doc.get("type")
|
|
127
|
+
if doc_type == constants.NodeTypes.DOC:
|
|
128
|
+
content = doc.get("content", [])
|
|
129
|
+
return "\n".join(self._serialize_nodes(content))
|
|
130
|
+
else:
|
|
131
|
+
return self._render_node(doc, None, 0)
|
|
132
|
+
|
|
133
|
+
def _serialize_nodes(self, nodes: list[dict]) -> list[str]:
|
|
134
|
+
"""Serialize a list of nodes.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
nodes: List of TipTap nodes
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
List of rendered strings
|
|
141
|
+
"""
|
|
142
|
+
output = []
|
|
143
|
+
for i, node in enumerate(nodes):
|
|
144
|
+
if node is None:
|
|
145
|
+
continue
|
|
146
|
+
output.append(self._render_node(node, None, i))
|
|
147
|
+
return output
|
|
148
|
+
|
|
149
|
+
def _render_node(
|
|
150
|
+
self,
|
|
151
|
+
node: dict,
|
|
152
|
+
parent_node: dict | None,
|
|
153
|
+
index: int,
|
|
154
|
+
) -> str:
|
|
155
|
+
"""Render a single node to Markdown.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
node: Node to render
|
|
159
|
+
parent_node: Parent node
|
|
160
|
+
index: Index in parent
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Rendered Markdown string
|
|
164
|
+
"""
|
|
165
|
+
node_type = node.get("type")
|
|
166
|
+
if node_type is None:
|
|
167
|
+
return ""
|
|
168
|
+
|
|
169
|
+
if node_type == constants.NodeTypes.TEXT:
|
|
170
|
+
return node.get("text", "")
|
|
171
|
+
|
|
172
|
+
renderer = self.get_node_renderer(node_type)
|
|
173
|
+
if renderer is None:
|
|
174
|
+
logger.warning("No renderer found for node type: %s", node_type)
|
|
175
|
+
return ""
|
|
176
|
+
|
|
177
|
+
parent_type = parent_node.get("type") if parent_node else None
|
|
178
|
+
|
|
179
|
+
content = node.get("content", [])
|
|
180
|
+
rendered_content = self._render_nodes(content, node)
|
|
181
|
+
|
|
182
|
+
return renderer(self, node, rendered_content, parent_type, index)
|
|
183
|
+
|
|
184
|
+
def _render_nodes(
|
|
185
|
+
self,
|
|
186
|
+
nodes: list[dict],
|
|
187
|
+
parent_node: dict,
|
|
188
|
+
) -> str:
|
|
189
|
+
"""Render nodes with mark state management.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
nodes: Nodes to render
|
|
193
|
+
parent_node: Parent node
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Rendered string with marks properly applied
|
|
197
|
+
"""
|
|
198
|
+
result: list[str] = []
|
|
199
|
+
active_marks: dict[str, dict] = {}
|
|
200
|
+
|
|
201
|
+
for i, node in enumerate(nodes):
|
|
202
|
+
if node is None:
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
node_type = node.get("type")
|
|
206
|
+
if node_type is None:
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
if node_type == constants.NodeTypes.TEXT:
|
|
210
|
+
text = node.get("text", "") or ""
|
|
211
|
+
|
|
212
|
+
current_marks = self._extract_marks(node)
|
|
213
|
+
|
|
214
|
+
marks_to_close = self._find_marks_to_close(active_marks, current_marks)
|
|
215
|
+
marks_to_open = self._find_marks_to_open(active_marks, current_marks)
|
|
216
|
+
|
|
217
|
+
for mark_type in marks_to_close:
|
|
218
|
+
opening, closing = self.get_mark_renderer(mark_type) or ("", "")
|
|
219
|
+
text += closing
|
|
220
|
+
del active_marks[mark_type]
|
|
221
|
+
|
|
222
|
+
for mark_type in marks_to_open:
|
|
223
|
+
opening, closing = self.get_mark_renderer(mark_type) or ("", "")
|
|
224
|
+
text = opening + text
|
|
225
|
+
active_marks[mark_type] = current_marks[mark_type]
|
|
226
|
+
|
|
227
|
+
next_node = nodes[i + 1] if i < len(nodes) - 1 else None
|
|
228
|
+
if next_node is not None:
|
|
229
|
+
next_node_type = next_node.get("type")
|
|
230
|
+
if next_node_type != constants.NodeTypes.TEXT:
|
|
231
|
+
for mark_type in list(active_marks.keys()):
|
|
232
|
+
opening, closing = self.get_mark_renderer(mark_type) or ("", "")
|
|
233
|
+
text += closing
|
|
234
|
+
del active_marks[mark_type]
|
|
235
|
+
else:
|
|
236
|
+
next_marks = self._extract_marks(next_node)
|
|
237
|
+
if not self._marks_equal(active_marks, next_marks):
|
|
238
|
+
for mark_type in list(active_marks.keys()):
|
|
239
|
+
opening, closing = self.get_mark_renderer(mark_type) or ("", "")
|
|
240
|
+
text += closing
|
|
241
|
+
del active_marks[mark_type]
|
|
242
|
+
|
|
243
|
+
result.append(text)
|
|
244
|
+
else:
|
|
245
|
+
marks_to_reopen = dict(active_marks)
|
|
246
|
+
active_marks.clear()
|
|
247
|
+
|
|
248
|
+
rendered = self._render_node(node, parent_node, i)
|
|
249
|
+
|
|
250
|
+
if node_type != constants.NodeTypes.HARD_BREAK:
|
|
251
|
+
for mark_type, mark in marks_to_reopen.items():
|
|
252
|
+
opening, closing = self.get_mark_renderer(mark_type) or ("", "")
|
|
253
|
+
rendered = opening + rendered
|
|
254
|
+
active_marks[mark_type] = mark
|
|
255
|
+
|
|
256
|
+
result.append(rendered)
|
|
257
|
+
|
|
258
|
+
for mark_type in list(active_marks.keys()):
|
|
259
|
+
opening, closing = self.get_mark_renderer(mark_type) or ("", "")
|
|
260
|
+
if result:
|
|
261
|
+
result[-1] += closing
|
|
262
|
+
|
|
263
|
+
return "".join(result)
|
|
264
|
+
|
|
265
|
+
# ==========================================================================
|
|
266
|
+
# Parsing (Markdown -> TipTap JSON)
|
|
267
|
+
# ==========================================================================
|
|
268
|
+
|
|
269
|
+
def parse(self, markdown: str) -> dict:
|
|
270
|
+
"""Parse Markdown text to TipTap JSON.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
markdown: Markdown text string
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
TipTap JSON document
|
|
277
|
+
"""
|
|
278
|
+
if not markdown:
|
|
279
|
+
return parse_utils.create_doc([])
|
|
280
|
+
|
|
281
|
+
return MarkdownToTipTapParser().parse(markdown)
|
|
282
|
+
|
|
283
|
+
# ==========================================================================
|
|
284
|
+
# Mark State Management
|
|
285
|
+
# ==========================================================================
|
|
286
|
+
|
|
287
|
+
def _extract_marks(self, node: dict) -> dict[str, dict]:
|
|
288
|
+
"""Extract marks from a node.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
node: Node to extract marks from
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Dict mapping mark type to mark
|
|
295
|
+
"""
|
|
296
|
+
marks: dict[str, dict] = {}
|
|
297
|
+
marks_array = node.get("marks")
|
|
298
|
+
if marks_array:
|
|
299
|
+
for mark in marks_array:
|
|
300
|
+
if mark and mark.get("type"):
|
|
301
|
+
marks[mark["type"]] = mark
|
|
302
|
+
return marks
|
|
303
|
+
|
|
304
|
+
def _find_marks_to_close(
|
|
305
|
+
self,
|
|
306
|
+
active_marks: dict[str, dict],
|
|
307
|
+
current_marks: dict[str, dict],
|
|
308
|
+
) -> list[str]:
|
|
309
|
+
"""Find marks that need to be closed.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
active_marks: Currently active marks
|
|
313
|
+
current_marks: Marks in current node
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
List of mark types to close
|
|
317
|
+
"""
|
|
318
|
+
return [mt for mt in active_marks if mt not in current_marks]
|
|
319
|
+
|
|
320
|
+
def _find_marks_to_open(
|
|
321
|
+
self,
|
|
322
|
+
active_marks: dict[str, dict],
|
|
323
|
+
current_marks: dict[str, dict],
|
|
324
|
+
) -> list[str]:
|
|
325
|
+
"""Find marks that need to be opened.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
active_marks: Currently active marks
|
|
329
|
+
current_marks: Marks in current node
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
List of mark types to open
|
|
333
|
+
"""
|
|
334
|
+
return [mt for mt in current_marks if mt not in active_marks]
|
|
335
|
+
|
|
336
|
+
def _marks_equal(
|
|
337
|
+
self,
|
|
338
|
+
marks1: dict[str, dict],
|
|
339
|
+
marks2: dict[str, dict],
|
|
340
|
+
) -> bool:
|
|
341
|
+
"""Compare two mark sets for equality.
|
|
342
|
+
|
|
343
|
+
Only compares format marks (bold, italic, etc.), not link marks.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
marks1: First mark set
|
|
347
|
+
marks2: Second mark set
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
True if marks are equal
|
|
351
|
+
"""
|
|
352
|
+
format_marks = [
|
|
353
|
+
constants.MarkTypes.BOLD,
|
|
354
|
+
constants.MarkTypes.ITALIC,
|
|
355
|
+
constants.MarkTypes.STRIKE,
|
|
356
|
+
constants.MarkTypes.CODE,
|
|
357
|
+
constants.MarkTypes.HIGHLIGHT,
|
|
358
|
+
constants.MarkTypes.UNDERLINE,
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
format_marks1 = {mt: marks1[mt] for mt in format_marks if mt in marks1}
|
|
362
|
+
format_marks2 = {mt: marks2[mt] for mt in format_marks if mt in marks2}
|
|
363
|
+
|
|
364
|
+
if len(format_marks1) != len(format_marks2):
|
|
365
|
+
return False
|
|
366
|
+
|
|
367
|
+
for mark_type in format_marks1:
|
|
368
|
+
if mark_type not in format_marks2:
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
attrs1 = format_marks1[mark_type].get("attrs")
|
|
372
|
+
attrs2 = format_marks2[mark_type].get("attrs")
|
|
373
|
+
|
|
374
|
+
if attrs1 is None and attrs2 is None:
|
|
375
|
+
continue
|
|
376
|
+
elif attrs1 is None or attrs2 is None or attrs1 != attrs2:
|
|
377
|
+
return False
|
|
378
|
+
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
# ==========================================================================
|
|
382
|
+
# Default Renderers
|
|
383
|
+
# ==========================================================================
|
|
384
|
+
|
|
385
|
+
def _register_default_renderers(self) -> None:
|
|
386
|
+
"""Register all default node and mark renderers."""
|
|
387
|
+
self.register_node_renderer(
|
|
388
|
+
constants.NodeTypes.PARAGRAPH,
|
|
389
|
+
lambda t, n, c, p, i: render_utils.render_paragraph(n, c, p),
|
|
390
|
+
)
|
|
391
|
+
self.register_node_renderer(
|
|
392
|
+
constants.NodeTypes.HEADING,
|
|
393
|
+
lambda t, n, c, p, i: render_utils.render_heading(
|
|
394
|
+
n, int(n.get("attrs", {}).get("level", 1)), c
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
self.register_node_renderer(
|
|
398
|
+
constants.NodeTypes.CODE_BLOCK,
|
|
399
|
+
lambda t, n, c, p, i: render_utils.render_code_block(n, c),
|
|
400
|
+
)
|
|
401
|
+
self.register_node_renderer(
|
|
402
|
+
constants.NodeTypes.BLOCKQUOTE,
|
|
403
|
+
lambda t, n, c, p, i: render_utils.render_blockquote(c),
|
|
404
|
+
)
|
|
405
|
+
self.register_node_renderer(
|
|
406
|
+
constants.NodeTypes.BULLET_LIST,
|
|
407
|
+
lambda t, n, c, p, i: render_utils.render_bullet_list(c),
|
|
408
|
+
)
|
|
409
|
+
self.register_node_renderer(
|
|
410
|
+
constants.NodeTypes.ORDERED_LIST,
|
|
411
|
+
lambda t, n, c, p, i: render_utils.render_ordered_list(c),
|
|
412
|
+
)
|
|
413
|
+
self.register_node_renderer(
|
|
414
|
+
constants.NodeTypes.LIST_ITEM,
|
|
415
|
+
lambda t, n, c, p, i: render_utils.render_list_item(n, c, p, i),
|
|
416
|
+
)
|
|
417
|
+
self.register_node_renderer(
|
|
418
|
+
constants.NodeTypes.HORIZONTAL_RULE,
|
|
419
|
+
lambda t, n, c, p, i: render_utils.render_horizontal_rule(),
|
|
420
|
+
)
|
|
421
|
+
self.register_node_renderer(
|
|
422
|
+
constants.NodeTypes.HARD_BREAK,
|
|
423
|
+
lambda t, n, c, p, i: render_utils.render_hard_break(),
|
|
424
|
+
)
|
|
425
|
+
self.register_node_renderer(
|
|
426
|
+
constants.NodeTypes.IMAGE,
|
|
427
|
+
lambda t, n, c, p, i: render_utils.render_image(n),
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if self.table_style == constants.TableStyle.MARKDOWN:
|
|
431
|
+
self.register_node_renderer(
|
|
432
|
+
constants.NodeTypes.TABLE,
|
|
433
|
+
lambda t, n, c, p, i: render_utils.render_table_to_md_format(n),
|
|
434
|
+
)
|
|
435
|
+
else:
|
|
436
|
+
self.register_node_renderer(
|
|
437
|
+
constants.NodeTypes.TABLE,
|
|
438
|
+
lambda t, n, c, p, i: render_utils.render_table_to_html_format(t, n),
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
self.register_node_renderer(
|
|
442
|
+
constants.NodeTypes.TABLE_ROW,
|
|
443
|
+
lambda t, n, c, p, i: render_utils.render_table_row(),
|
|
444
|
+
)
|
|
445
|
+
self.register_node_renderer(
|
|
446
|
+
constants.NodeTypes.TABLE_CELL,
|
|
447
|
+
lambda t, n, c, p, i: render_utils.render_table_cell(),
|
|
448
|
+
)
|
|
449
|
+
self.register_node_renderer(
|
|
450
|
+
constants.NodeTypes.TABLE_HEADER,
|
|
451
|
+
lambda t, n, c, p, i: render_utils.render_table_header(),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
self.register_node_renderer(
|
|
455
|
+
constants.NodeTypes.FIXED_TITLE,
|
|
456
|
+
lambda t, n, c, p, i: render_utils.render_fixed_title(c),
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
self.register_node_renderer(
|
|
460
|
+
constants.NodeTypes.CALLOUT,
|
|
461
|
+
lambda t, n, c, p, i: render_utils.render_callout(n, c),
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
self.register_node_renderer(
|
|
465
|
+
constants.NodeTypes.MENTION,
|
|
466
|
+
lambda t, n, c, p, i: render_utils.render_mention(n),
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
self.register_node_renderer(
|
|
470
|
+
constants.NodeTypes.MERMAID,
|
|
471
|
+
lambda t, n, c, p, i: render_utils.render_mermaid(n),
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
self.register_node_renderer(
|
|
475
|
+
constants.NodeTypes.COLUMNS,
|
|
476
|
+
lambda t, n, c, p, i: self._render_columns(n),
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
self.register_node_renderer(
|
|
480
|
+
constants.NodeTypes.AI_SKILL,
|
|
481
|
+
lambda t, n, c, p, i: self._render_ai_skill(n),
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
self.register_node_renderer(
|
|
485
|
+
constants.NodeTypes.INSIGHT_BLOCK,
|
|
486
|
+
lambda t, n, c, p, i: self._render_insight_block(n),
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
self.register_node_renderer(
|
|
490
|
+
constants.NodeTypes.WIDGET_VIEW,
|
|
491
|
+
lambda t, n, c, p, i: render_utils.render_single_item_custom(
|
|
492
|
+
n, constants.NodeTypes.WIDGET_VIEW
|
|
493
|
+
),
|
|
494
|
+
)
|
|
495
|
+
self.register_node_renderer(
|
|
496
|
+
constants.NodeTypes.WIDGET_NUM,
|
|
497
|
+
lambda t, n, c, p, i: render_utils.render_single_item_custom(
|
|
498
|
+
n, constants.NodeTypes.WIDGET_NUM, True
|
|
499
|
+
),
|
|
500
|
+
)
|
|
501
|
+
self.register_node_renderer(
|
|
502
|
+
constants.NodeTypes.DATASET_DATA,
|
|
503
|
+
lambda t, n, c, p, i: render_utils.render_single_item_custom(
|
|
504
|
+
n, constants.NodeTypes.DATASET_DATA
|
|
505
|
+
),
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
self.register_mark_renderer(
|
|
509
|
+
constants.MarkTypes.BOLD,
|
|
510
|
+
constants.Markdown.BOLD_MARKER,
|
|
511
|
+
constants.Markdown.BOLD_MARKER,
|
|
512
|
+
)
|
|
513
|
+
self.register_mark_renderer(
|
|
514
|
+
constants.MarkTypes.ITALIC,
|
|
515
|
+
constants.Markdown.ITALIC_MARKER,
|
|
516
|
+
constants.Markdown.ITALIC_MARKER,
|
|
517
|
+
)
|
|
518
|
+
self.register_mark_renderer(
|
|
519
|
+
constants.MarkTypes.STRIKE,
|
|
520
|
+
constants.Markdown.STRIKE_MARKER,
|
|
521
|
+
constants.Markdown.STRIKE_MARKER,
|
|
522
|
+
)
|
|
523
|
+
self.register_mark_renderer(
|
|
524
|
+
constants.MarkTypes.CODE,
|
|
525
|
+
constants.Markdown.CODE_MARKER,
|
|
526
|
+
constants.Markdown.CODE_MARKER,
|
|
527
|
+
)
|
|
528
|
+
self.register_mark_renderer(
|
|
529
|
+
constants.MarkTypes.LINK,
|
|
530
|
+
"",
|
|
531
|
+
"",
|
|
532
|
+
)
|
|
533
|
+
self.register_mark_renderer(
|
|
534
|
+
constants.MarkTypes.UNDERLINE,
|
|
535
|
+
constants.Markdown.UNDERLINE_HTML_OPEN,
|
|
536
|
+
constants.Markdown.UNDERLINE_HTML_CLOSE,
|
|
537
|
+
)
|
|
538
|
+
self.register_mark_renderer(
|
|
539
|
+
constants.MarkTypes.HIGHLIGHT,
|
|
540
|
+
constants.Markdown.HIGHLIGHT_MARKER,
|
|
541
|
+
constants.Markdown.HIGHLIGHT_MARKER,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
def _render_columns(self, node: dict) -> str:
|
|
545
|
+
"""Render columns container.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
node: Columns node
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
Rendered columns HTML
|
|
552
|
+
"""
|
|
553
|
+
attrs = node.get("attrs") or {}
|
|
554
|
+
count = attrs.get(constants.AttrTypes.COLUMNS_COUNT, 2)
|
|
555
|
+
gap = attrs.get(constants.AttrTypes.COLUMNS_GAP)
|
|
556
|
+
|
|
557
|
+
content = node.get("content")
|
|
558
|
+
if not content:
|
|
559
|
+
return ""
|
|
560
|
+
|
|
561
|
+
column_nodes: list[dict] = []
|
|
562
|
+
for item in content:
|
|
563
|
+
if not item:
|
|
564
|
+
continue
|
|
565
|
+
if item.get("type") == constants.NodeTypes.COLUMN:
|
|
566
|
+
column_nodes.append(item)
|
|
567
|
+
|
|
568
|
+
columns_content: list[str] = []
|
|
569
|
+
for column in column_nodes:
|
|
570
|
+
column_attrs = column.get("attrs") or {}
|
|
571
|
+
width = column_attrs.get(constants.AttrTypes.COLUMN_WIDTH)
|
|
572
|
+
|
|
573
|
+
column_content = column.get("content")
|
|
574
|
+
if column_content:
|
|
575
|
+
column_html = self._render_nodes(column_content, column)
|
|
576
|
+
else:
|
|
577
|
+
column_html = ""
|
|
578
|
+
|
|
579
|
+
width_attr = f' width="{width}"' if width else ""
|
|
580
|
+
columns_content.append(f"<column{width_attr}>\n{column_html}\n</column>\n")
|
|
581
|
+
|
|
582
|
+
gap_attr = f' gap="{gap}"' if gap else ""
|
|
583
|
+
return (
|
|
584
|
+
f'<anti:html type="columns" count="{count}"{gap_attr}>\n'
|
|
585
|
+
+ "".join(columns_content)
|
|
586
|
+
+ "</anti:html>\n"
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
def _render_ai_skill(self, node: dict) -> str:
|
|
590
|
+
"""Render AI skill block.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
node: AI skill node
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
Rendered AI skill HTML
|
|
597
|
+
"""
|
|
598
|
+
html_tag = constants.NodeTypes.AI_SKILL
|
|
599
|
+
attrs = node.get("attrs") or {}
|
|
600
|
+
|
|
601
|
+
html_attrs = render_utils.attrs_to_html_attrs(attrs)
|
|
602
|
+
|
|
603
|
+
content = node.get("content")
|
|
604
|
+
if content:
|
|
605
|
+
html_buf = self._render_nodes(content, node)
|
|
606
|
+
else:
|
|
607
|
+
html_buf = ""
|
|
608
|
+
|
|
609
|
+
return f"<{html_tag}{html_attrs}>\n{html_buf}</{html_tag}>\n"
|
|
610
|
+
|
|
611
|
+
def _render_insight_block(self, node: dict) -> str:
|
|
612
|
+
"""Render insight block.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
node: Insight block node
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
Rendered insight block
|
|
619
|
+
"""
|
|
620
|
+
content = node.get("content")
|
|
621
|
+
if not content:
|
|
622
|
+
return ""
|
|
623
|
+
|
|
624
|
+
rendered_parts = []
|
|
625
|
+
for child in content:
|
|
626
|
+
if child:
|
|
627
|
+
rendered_parts.append(self.serialize(child))
|
|
628
|
+
|
|
629
|
+
return "\n".join(rendered_parts)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .common import (
|
|
2
|
+
calculate_file_hash,
|
|
3
|
+
generate_slug_id,
|
|
4
|
+
get_log_dir,
|
|
5
|
+
get_log_path,
|
|
6
|
+
get_root_dir,
|
|
7
|
+
get_unix_timestamp,
|
|
8
|
+
)
|
|
9
|
+
from .utils import (
|
|
10
|
+
decrypt_body,
|
|
11
|
+
encrypt_body,
|
|
12
|
+
encrypt_hex,
|
|
13
|
+
filter_nodes_by_pattern,
|
|
14
|
+
get_work_dir,
|
|
15
|
+
)
|