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.

Files changed (79) hide show
  1. dataops_sdk/__init__.py +84 -0
  2. dataops_sdk/constants.py +6 -0
  3. dataops_sdk/exceptions.py +31 -0
  4. dataops_sdk/runner/__init__.py +46 -0
  5. dataops_sdk/runner/datasource_factory.py +83 -0
  6. dataops_sdk/runner/schemas/nacos/__init__.py +5 -0
  7. dataops_sdk/runner/schemas/nacos/config.py +40 -0
  8. dataops_sdk/runner/schemas/nacos/constants.py +235 -0
  9. dataops_sdk/runner/schemas/nacos/instance.py +103 -0
  10. dataops_sdk/runner/schemas/nacos/nacos_exception.py +44 -0
  11. dataops_sdk/runner/schemas/nacos/naming_param.py +62 -0
  12. dataops_sdk/runner/schemas/nacos/naming_request.py +84 -0
  13. dataops_sdk/runner/schemas/nacos/naming_response.py +50 -0
  14. dataops_sdk/runner/schemas/nacos/preserved_metadata_key.py +10 -0
  15. dataops_sdk/runner/schemas/nacos/service.py +119 -0
  16. dataops_sdk/runner/utils/__init__.py +42 -0
  17. dataops_sdk/runner/utils/common.py +209 -0
  18. dataops_sdk/runner/utils/dbConn_base.py +256 -0
  19. dataops_sdk/runner/utils/dbConn_sync.py +485 -0
  20. dataops_sdk/runner/utils/duckdb_manager.py +203 -0
  21. dataops_sdk/runner/utils/file_transfer_base.py +307 -0
  22. dataops_sdk/runner/utils/ftp_file_transfer.py +242 -0
  23. dataops_sdk/runner/utils/log.py +50 -0
  24. dataops_sdk/runner/utils/nacos.py +96 -0
  25. dataops_sdk/runner/utils/neo4j_sync.py +236 -0
  26. dataops_sdk/runner/utils/queue_base.py +224 -0
  27. dataops_sdk/runner/utils/queue_kafka.py +150 -0
  28. dataops_sdk/runner/utils/sftp_file_transfer.py +243 -0
  29. dataops_sdk/schemas/__init__.py +32 -0
  30. dataops_sdk/schemas/alarm.py +38 -0
  31. dataops_sdk/schemas/api.py +37 -0
  32. dataops_sdk/schemas/data_table.py +437 -0
  33. dataops_sdk/schemas/datasource.py +26 -0
  34. dataops_sdk/schemas/dict_.py +13 -0
  35. dataops_sdk/schemas/enums.py +120 -0
  36. dataops_sdk/schemas/group.py +40 -0
  37. dataops_sdk/schemas/hazard.py +151 -0
  38. dataops_sdk/schemas/meta.py +175 -0
  39. dataops_sdk/schemas/query.py +175 -0
  40. dataops_sdk/schemas/report.py +22 -0
  41. dataops_sdk/schemas/skill.py +70 -0
  42. dataops_sdk/schemas/table.py +65 -0
  43. dataops_sdk/schemas/user.py +52 -0
  44. dataops_sdk/schemas/widget.py +651 -0
  45. dataops_sdk/schemas/workflow.py +63 -0
  46. dataops_sdk/services/__init__.py +110 -0
  47. dataops_sdk/services/alarm.py +324 -0
  48. dataops_sdk/services/api.py +28 -0
  49. dataops_sdk/services/auth.py +120 -0
  50. dataops_sdk/services/data_table.py +485 -0
  51. dataops_sdk/services/dataset.py +127 -0
  52. dataops_sdk/services/datasource.py +57 -0
  53. dataops_sdk/services/dict_.py +69 -0
  54. dataops_sdk/services/hazard.py +260 -0
  55. dataops_sdk/services/meta.py +523 -0
  56. dataops_sdk/services/metrics.py +42 -0
  57. dataops_sdk/services/ref.py +66 -0
  58. dataops_sdk/services/report.py +36 -0
  59. dataops_sdk/services/skill.py +276 -0
  60. dataops_sdk/services/table.py +340 -0
  61. dataops_sdk/services/widget.py +143 -0
  62. dataops_sdk/services/workflow.py +102 -0
  63. dataops_sdk/session.py +292 -0
  64. dataops_sdk/tiptap/__init__.py +30 -0
  65. dataops_sdk/tiptap/builder.py +75 -0
  66. dataops_sdk/tiptap/constants.py +133 -0
  67. dataops_sdk/tiptap/markdown_parser.py +294 -0
  68. dataops_sdk/tiptap/models.py +43 -0
  69. dataops_sdk/tiptap/parse_utils.py +889 -0
  70. dataops_sdk/tiptap/render_utils.py +846 -0
  71. dataops_sdk/tiptap/renderers.py +51 -0
  72. dataops_sdk/tiptap/tool.py +629 -0
  73. dataops_sdk/utils/__init__.py +15 -0
  74. dataops_sdk/utils/common.py +211 -0
  75. dataops_sdk/utils/utils.py +120 -0
  76. dataops_sdk-0.3.0.dist-info/METADATA +25 -0
  77. dataops_sdk-0.3.0.dist-info/RECORD +79 -0
  78. dataops_sdk-0.3.0.dist-info/WHEEL +5 -0
  79. 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
+ )