docspan 0.1.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.
Files changed (65) hide show
  1. docspan/__init__.py +3 -0
  2. docspan/__main__.py +0 -0
  3. docspan/backends/__init__.py +19 -0
  4. docspan/backends/base.py +85 -0
  5. docspan/backends/confluence/__init__.py +0 -0
  6. docspan/backends/confluence/adf/__init__.py +14 -0
  7. docspan/backends/confluence/adf/comparator.py +427 -0
  8. docspan/backends/confluence/adf/converter.py +119 -0
  9. docspan/backends/confluence/adf/converters.py +1449 -0
  10. docspan/backends/confluence/adf/interfaces.py +191 -0
  11. docspan/backends/confluence/adf/nodes.py +2085 -0
  12. docspan/backends/confluence/adf/parser.py +400 -0
  13. docspan/backends/confluence/adf/validators.py +161 -0
  14. docspan/backends/confluence/adf/visitors.py +495 -0
  15. docspan/backends/confluence/backend.py +227 -0
  16. docspan/backends/confluence/client.py +44 -0
  17. docspan/backends/confluence/config/__init__.py +21 -0
  18. docspan/backends/confluence/config/loader.py +107 -0
  19. docspan/backends/confluence/config/models.py +167 -0
  20. docspan/backends/confluence/config/validation.py +297 -0
  21. docspan/backends/confluence/markdown/__init__.py +22 -0
  22. docspan/backends/confluence/markdown/ast.py +819 -0
  23. docspan/backends/confluence/markdown/extensions/__init__.py +5 -0
  24. docspan/backends/confluence/markdown/extensions/frontmatter.py +80 -0
  25. docspan/backends/confluence/markdown/extensions/mermaid.py +64 -0
  26. docspan/backends/confluence/markdown/extensions/wikilinks.py +179 -0
  27. docspan/backends/confluence/markdown/inline_parser.py +495 -0
  28. docspan/backends/confluence/markdown/parser.py +1006 -0
  29. docspan/backends/confluence/models/__init__.py +18 -0
  30. docspan/backends/confluence/models/markdown_file.py +402 -0
  31. docspan/backends/confluence/models/page.py +212 -0
  32. docspan/backends/confluence/models/path_utils.py +34 -0
  33. docspan/backends/confluence/models/results.py +28 -0
  34. docspan/backends/confluence/models/sync_status.py +382 -0
  35. docspan/backends/confluence/services/__init__.py +0 -0
  36. docspan/backends/confluence/services/confluence/__init__.py +40 -0
  37. docspan/backends/confluence/services/confluence/attachment_client.py +147 -0
  38. docspan/backends/confluence/services/confluence/base_client.py +420 -0
  39. docspan/backends/confluence/services/confluence/client.py +376 -0
  40. docspan/backends/confluence/services/confluence/comment_client.py +682 -0
  41. docspan/backends/confluence/services/confluence/crawler.py +587 -0
  42. docspan/backends/confluence/services/confluence/label_client.py +130 -0
  43. docspan/backends/confluence/services/confluence/page_client.py +1288 -0
  44. docspan/backends/confluence/services/confluence/space_client.py +179 -0
  45. docspan/backends/confluence/services/confluence/url_parser.py +106 -0
  46. docspan/backends/google_docs/__init__.py +0 -0
  47. docspan/backends/google_docs/auth.py +143 -0
  48. docspan/backends/google_docs/backend.py +140 -0
  49. docspan/backends/google_docs/client.py +665 -0
  50. docspan/backends/google_docs/converter.py +471 -0
  51. docspan/backends/google_docs/docs_request_builder.py +232 -0
  52. docspan/backends/google_docs/docs_structure_parser.py +120 -0
  53. docspan/backends/google_docs/markdown_to_paragraph_parser.py +145 -0
  54. docspan/cli/__init__.py +0 -0
  55. docspan/cli/main.py +408 -0
  56. docspan/config.py +62 -0
  57. docspan/core/__init__.py +49 -0
  58. docspan/core/merge.py +30 -0
  59. docspan/core/orchestrator.py +332 -0
  60. docspan/core/paths.py +8 -0
  61. docspan/core/state.py +53 -0
  62. docspan-0.1.0.dist-info/METADATA +273 -0
  63. docspan-0.1.0.dist-info/RECORD +65 -0
  64. docspan-0.1.0.dist-info/WHEEL +4 -0
  65. docspan-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,819 @@
1
+ """
2
+ Abstract Syntax Tree nodes for Markdown parsing.
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Dict, List, Optional
7
+
8
+
9
+ @dataclass
10
+ class MarkdownNode:
11
+ """
12
+ Base class for nodes in the Markdown Abstract Syntax Tree (AST).
13
+
14
+ Attributes:
15
+ type: Node type
16
+ content: Optional raw content
17
+ children: List of child nodes
18
+ attrs: Additional attributes
19
+ """
20
+
21
+ type: str = ""
22
+ content: Optional[str] = None
23
+ children: List["MarkdownNode"] = field(default_factory=list)
24
+ attrs: Dict[str, Any] = field(default_factory=dict)
25
+
26
+
27
+ @dataclass
28
+ class TextNode(MarkdownNode):
29
+ """
30
+ Node for plain text.
31
+
32
+ Attributes:
33
+ content: Text content
34
+ marks: Text formatting marks (bold, italic, etc.)
35
+ """
36
+
37
+ marks: List[Dict[str, Any]] = field(default_factory=list)
38
+
39
+ def __post_init__(self) -> None:
40
+ """Initialize type."""
41
+ self.type = "text"
42
+
43
+
44
+ @dataclass
45
+ class HeadingNode(MarkdownNode):
46
+ """
47
+ Node for headings.
48
+
49
+ Attributes:
50
+ level: Heading level (1-6)
51
+ """
52
+
53
+ level: int = 1
54
+
55
+ def __post_init__(self) -> None:
56
+ """Initialize type."""
57
+ self.type = "heading"
58
+
59
+
60
+ @dataclass
61
+ class ParagraphNode(MarkdownNode):
62
+ """Node for paragraphs."""
63
+
64
+ def __post_init__(self) -> None:
65
+ """Initialize type."""
66
+ self.type = "paragraph"
67
+
68
+
69
+ @dataclass
70
+ class ListItemNode(MarkdownNode):
71
+ """
72
+ Node for list items.
73
+
74
+ Attributes:
75
+ bullet: Bullet character or number
76
+ """
77
+
78
+ bullet: str = "*"
79
+
80
+ def __post_init__(self) -> None:
81
+ """Initialize type."""
82
+ self.type = "listItem"
83
+
84
+
85
+ @dataclass
86
+ class BulletListNode(MarkdownNode):
87
+ """Node for bullet lists."""
88
+
89
+ def __post_init__(self) -> None:
90
+ """Initialize type."""
91
+ self.type = "bulletList"
92
+
93
+
94
+ @dataclass
95
+ class OrderedListNode(MarkdownNode):
96
+ """Node for ordered lists."""
97
+
98
+ def __post_init__(self) -> None:
99
+ """Initialize type."""
100
+ self.type = "orderedList"
101
+
102
+
103
+ @dataclass
104
+ class CodeBlockNode(MarkdownNode):
105
+ """
106
+ Node for code blocks.
107
+
108
+ Attributes:
109
+ language: Programming language for syntax highlighting
110
+ """
111
+
112
+ language: Optional[str] = None
113
+
114
+ def __post_init__(self) -> None:
115
+ """Initialize type."""
116
+ self.type = "codeBlock"
117
+
118
+
119
+ @dataclass
120
+ class InlineCodeNode(MarkdownNode):
121
+ """Node for inline code."""
122
+
123
+ def __post_init__(self) -> None:
124
+ """Initialize type."""
125
+ self.type = "inlineCode"
126
+
127
+
128
+ @dataclass
129
+ class BlockquoteNode(MarkdownNode):
130
+ """Node for blockquotes."""
131
+
132
+ def __post_init__(self) -> None:
133
+ """Initialize type."""
134
+ self.type = "blockquote"
135
+
136
+
137
+ @dataclass
138
+ class TableNode(MarkdownNode):
139
+ """
140
+ Node for tables.
141
+
142
+ Attributes:
143
+ headers: Table headers
144
+ rows: Table rows
145
+ """
146
+
147
+ headers: List[str] = field(default_factory=list)
148
+ rows: List[List[MarkdownNode]] = field(default_factory=list)
149
+
150
+ def __post_init__(self) -> None:
151
+ """Initialize type."""
152
+ self.type = "table"
153
+
154
+
155
+ @dataclass
156
+ class LinkNode(MarkdownNode):
157
+ """
158
+ Node for links.
159
+
160
+ Attributes:
161
+ url: Link URL
162
+ title: Link title
163
+ """
164
+
165
+ url: str = ""
166
+ title: Optional[str] = None
167
+
168
+ def __post_init__(self) -> None:
169
+ """Initialize type."""
170
+ self.type = "link"
171
+
172
+
173
+ @dataclass
174
+ class ImageNode(MarkdownNode):
175
+ """
176
+ Node for images.
177
+
178
+ Attributes:
179
+ src: Image source URL or Confluence file ID
180
+ alt: Alternative text
181
+ title: Image title
182
+ width: Image width (pixels or percentage)
183
+ height: Image height (pixels)
184
+ layout: Layout option (center, wide, full-width, wrap-left, wrap-right)
185
+ is_confluence_attachment: True if using Confluence file ID instead of external URL
186
+ collection: Confluence Media Services collection name (for Confluence attachments)
187
+ occurrence_key: Confluence occurrence key (for deletion support)
188
+ """
189
+
190
+ src: str = ""
191
+ alt: str = ""
192
+ title: Optional[str] = None
193
+ width: Optional[int] = None
194
+ height: Optional[int] = None
195
+ layout: Optional[str] = None # center, wide, full-width, wrap-left, wrap-right
196
+ is_confluence_attachment: bool = False
197
+ collection: Optional[str] = None
198
+ occurrence_key: Optional[str] = None
199
+
200
+ def __post_init__(self) -> None:
201
+ """Initialize type."""
202
+ self.type = "image"
203
+
204
+
205
+ @dataclass
206
+ class MediaGroupNode(MarkdownNode):
207
+ """
208
+ Node for media groups (image galleries).
209
+
210
+ A container for multiple media items displayed together, commonly used
211
+ for image galleries or multiple file attachments.
212
+
213
+ Attributes:
214
+ children: List of ImageNode items in the group
215
+ """
216
+
217
+ def __post_init__(self) -> None:
218
+ """Initialize type."""
219
+ self.type = "mediaGroup"
220
+
221
+
222
+ @dataclass
223
+ class WikiLinkNode(MarkdownNode):
224
+ """
225
+ Node for wiki-style links ([[page]] or [[page|title]]).
226
+
227
+ Attributes:
228
+ target: Link target (page name)
229
+ display: Display text
230
+ """
231
+
232
+ target: str = ""
233
+ display: Optional[str] = None
234
+
235
+ def __post_init__(self) -> None:
236
+ """Initialize type."""
237
+ self.type = "wikiLink"
238
+
239
+
240
+ @dataclass
241
+ class MentionNode(MarkdownNode):
242
+ """
243
+ Node for user mentions (@username).
244
+
245
+ Attributes:
246
+ username: Username being mentioned (without @ prefix)
247
+ user_id: Confluence user ID (optional, resolved during conversion)
248
+ text: Display text for the mention (defaults to @username)
249
+ """
250
+
251
+ username: str = ""
252
+ user_id: Optional[str] = None
253
+ text: Optional[str] = None
254
+
255
+ def __post_init__(self) -> None:
256
+ """Initialize type."""
257
+ self.type = "mention"
258
+ # Set default text if not provided
259
+ if not self.text and self.username:
260
+ self.text = f"@{self.username}"
261
+
262
+
263
+ @dataclass
264
+ class EmojiNode(MarkdownNode):
265
+ """
266
+ Node for emoji expressions (:emoji_name:).
267
+
268
+ Attributes:
269
+ short_name: Emoji short name (e.g., "smile", "tada", "rocket")
270
+ emoji_id: Confluence emoji ID (optional, resolved during conversion)
271
+ text: Fallback text representation (defaults to :short_name:)
272
+ """
273
+
274
+ short_name: str = ""
275
+ emoji_id: Optional[str] = None
276
+ text: Optional[str] = None
277
+
278
+ def __post_init__(self) -> None:
279
+ """Initialize type."""
280
+ self.type = "emoji"
281
+ # Set default text if not provided
282
+ if not self.text and self.short_name:
283
+ self.text = f":{self.short_name}:"
284
+
285
+
286
+ @dataclass
287
+ class ExpandNode(MarkdownNode):
288
+ """
289
+ Node for collapsible/expandable sections.
290
+
291
+ Converts HTML <details><summary> tags to Confluence expand nodes.
292
+
293
+ Attributes:
294
+ title: Title shown in the expand header (from <summary> tag)
295
+ children: Child nodes contained within the expand section
296
+ """
297
+
298
+ title: str = ""
299
+
300
+ def __post_init__(self) -> None:
301
+ """Initialize type."""
302
+ self.type = "expand"
303
+
304
+
305
+ @dataclass
306
+ class HorizontalRuleNode(MarkdownNode):
307
+ """Node for horizontal rules."""
308
+
309
+ def __post_init__(self) -> None:
310
+ """Initialize type."""
311
+ self.type = "horizontalRule"
312
+
313
+
314
+ @dataclass
315
+ class MermaidNode(MarkdownNode):
316
+ """
317
+ Node for Mermaid diagrams.
318
+
319
+ Attributes:
320
+ code: Mermaid diagram code
321
+ """
322
+
323
+ code: str = ""
324
+
325
+ def __post_init__(self) -> None:
326
+ """Initialize type."""
327
+ self.type = "mermaid"
328
+
329
+
330
+ @dataclass
331
+ class TaskListNode(MarkdownNode):
332
+ """
333
+ Node for task lists (checklists).
334
+
335
+ Converts GitHub-style task lists to Confluence task lists.
336
+
337
+ Attributes:
338
+ children: List of TaskItemNode items
339
+ """
340
+
341
+ def __post_init__(self) -> None:
342
+ """Initialize type."""
343
+ self.type = "taskList"
344
+
345
+
346
+ @dataclass
347
+ class TaskItemNode(MarkdownNode):
348
+ """
349
+ Node for individual task items in a task list.
350
+
351
+ Attributes:
352
+ checked: Whether the task is checked/completed
353
+ children: Content of the task item (typically paragraphs)
354
+ """
355
+
356
+ checked: bool = False
357
+
358
+ def __post_init__(self) -> None:
359
+ """Initialize type."""
360
+ self.type = "taskItem"
361
+
362
+
363
+ @dataclass
364
+ class StatusBadgeNode(MarkdownNode):
365
+ """
366
+ Node for status badges/lozenges.
367
+
368
+ Converts [!STATUS] syntax to Confluence status lozenges.
369
+
370
+ Attributes:
371
+ text: Status text (e.g., "DONE", "IN PROGRESS")
372
+ color: Status color (neutral, blue, green, yellow, red, purple)
373
+ """
374
+
375
+ text: str = ""
376
+ color: str = "neutral"
377
+
378
+ def __post_init__(self) -> None:
379
+ """Initialize type."""
380
+ self.type = "statusBadge"
381
+
382
+
383
+ @dataclass
384
+ class DateNode(MarkdownNode):
385
+ """
386
+ Node for date fields.
387
+
388
+ Converts ISO date strings to Confluence date nodes.
389
+
390
+ Attributes:
391
+ timestamp: Unix timestamp in milliseconds
392
+ date_string: Original date string (for display)
393
+ """
394
+
395
+ timestamp: int = 0
396
+ date_string: str = ""
397
+
398
+ def __post_init__(self) -> None:
399
+ """Initialize type."""
400
+ self.type = "date"
401
+
402
+
403
+ @dataclass
404
+ class LayoutSectionNode(MarkdownNode):
405
+ """
406
+ Node for multi-column layout sections.
407
+
408
+ Converts ::: columns syntax to Confluence layout sections.
409
+
410
+ Attributes:
411
+ children: List of LayoutColumnNode items
412
+ """
413
+
414
+ def __post_init__(self) -> None:
415
+ """Initialize type."""
416
+ self.type = "layoutSection"
417
+
418
+
419
+ @dataclass
420
+ class LayoutColumnNode(MarkdownNode):
421
+ """
422
+ Node for individual columns within a layout section.
423
+
424
+ Attributes:
425
+ width: Column width percentage (e.g., 50 for 50%)
426
+ children: Content within the column
427
+ """
428
+
429
+ width: Optional[int] = None
430
+
431
+ def __post_init__(self) -> None:
432
+ """Initialize type."""
433
+ self.type = "layoutColumn"
434
+
435
+
436
+ @dataclass
437
+ class HighlightedTextNode(MarkdownNode):
438
+ """
439
+ Node for highlighted/marked text.
440
+
441
+ Converts ==text== syntax to highlighted text with background color.
442
+
443
+ Attributes:
444
+ content: Text content
445
+ bg_color: Background color (default: yellow #ffff00)
446
+ marks: Additional text formatting marks
447
+ """
448
+
449
+ bg_color: str = "#ffff00"
450
+ marks: List[Dict[str, Any]] = field(default_factory=list)
451
+
452
+ def __post_init__(self) -> None:
453
+ """Initialize type."""
454
+ self.type = "highlightedText"
455
+
456
+
457
+ @dataclass
458
+ class ColoredTextNode(MarkdownNode):
459
+ """
460
+ Node for colored text.
461
+
462
+ Converts <mark style="color:..."> syntax to colored text.
463
+
464
+ Attributes:
465
+ content: Text content
466
+ color: Text color (hex code or CSS color name)
467
+ bg_color: Optional background color
468
+ marks: Additional text formatting marks
469
+ """
470
+
471
+ color: Optional[str] = None
472
+ bg_color: Optional[str] = None
473
+ marks: List[Dict[str, Any]] = field(default_factory=list)
474
+
475
+ def __post_init__(self) -> None:
476
+ """Initialize type."""
477
+ self.type = "coloredText"
478
+
479
+
480
+ @dataclass
481
+ class URLEmbedNode(MarkdownNode):
482
+ """
483
+ Node for embedded URLs (videos, rich content).
484
+
485
+ Auto-detects URLs and creates appropriate embed cards.
486
+
487
+ Attributes:
488
+ url: URL to embed
489
+ embed_type: Type of embed (video, card)
490
+ layout: Layout mode (center, wide, full-width)
491
+ width: Optional width in pixels
492
+ height: Optional height in pixels
493
+ """
494
+
495
+ url: str = ""
496
+ embed_type: str = "card" # video, card
497
+ layout: str = "center"
498
+ width: Optional[int] = None
499
+ height: Optional[int] = None
500
+
501
+ def __post_init__(self) -> None:
502
+ """Initialize type."""
503
+ self.type = "urlEmbed"
504
+
505
+
506
+ @dataclass
507
+ class ExtensionNode(MarkdownNode):
508
+ """
509
+ Base class for Confluence macro/extension nodes.
510
+
511
+ Extensions represent Confluence-specific functionality that goes beyond
512
+ standard markdown, such as TOC, info panels, status lozenges, etc.
513
+
514
+ Attributes:
515
+ extension_key: Macro name (e.g., "toc", "excerpt", "info")
516
+ parameters: Macro parameters as key-value pairs
517
+ extension_type: Confluence extension type (default: com.atlassian.confluence.macro.core)
518
+ """
519
+
520
+ extension_key: str = ""
521
+ parameters: Dict[str, str] = field(default_factory=dict)
522
+ extension_type: str = "com.atlassian.confluence.macro.core"
523
+
524
+ def __post_init__(self) -> None:
525
+ """Initialize type."""
526
+ self.type = "extension"
527
+
528
+
529
+ @dataclass
530
+ class TocNode(ExtensionNode):
531
+ """
532
+ Node for Table of Contents macro.
533
+
534
+ Generates an automatic table of contents from page headings.
535
+
536
+ Attributes:
537
+ max_level: Maximum heading level to include (1-7, default: 7)
538
+ min_level: Minimum heading level to include (1-7, default: 1)
539
+ include: Regex pattern for headings to include
540
+ exclude: Regex pattern for headings to exclude
541
+ toc_type: Display style - "list" or "flat"
542
+ printable: Include printable version
543
+ separator: Separator for flat type
544
+ """
545
+
546
+ max_level: int = 7
547
+ min_level: int = 1
548
+ include: Optional[str] = None
549
+ exclude: Optional[str] = None
550
+ toc_type: str = "list" # list or flat
551
+ printable: bool = True
552
+ separator: str = "dots"
553
+
554
+ def __post_init__(self) -> None:
555
+ """Initialize type and extension_key."""
556
+ self.type = "toc"
557
+ self.extension_key = "toc"
558
+
559
+ # Build parameters dict from attributes
560
+ self.parameters = {}
561
+ if self.max_level != 7:
562
+ self.parameters["maxLevel"] = str(self.max_level)
563
+ if self.min_level != 1:
564
+ self.parameters["minLevel"] = str(self.min_level)
565
+ if self.include:
566
+ self.parameters["include"] = self.include
567
+ if self.exclude:
568
+ self.parameters["exclude"] = self.exclude
569
+ if self.toc_type != "list":
570
+ self.parameters["type"] = self.toc_type
571
+ if not self.printable:
572
+ self.parameters["printable"] = "false"
573
+ if self.separator != "dots":
574
+ self.parameters["separator"] = self.separator
575
+
576
+
577
+ @dataclass
578
+ class ExcerptNode(ExtensionNode):
579
+ """
580
+ Node for Excerpt macro.
581
+
582
+ Defines a page excerpt that appears in search results and page listings.
583
+ The excerpt content is stored in the children list.
584
+
585
+ Attributes:
586
+ hidden: Hide excerpt from page content (only show in listings)
587
+ atlassian_macro_output_type: Output type (BLOCK or INLINE)
588
+ """
589
+
590
+ hidden: bool = False
591
+ atlassian_macro_output_type: str = "BLOCK"
592
+
593
+ def __post_init__(self) -> None:
594
+ """Initialize type and extension_key."""
595
+ self.type = "excerpt"
596
+ self.extension_key = "excerpt"
597
+
598
+ # Build parameters dict from attributes
599
+ self.parameters = {}
600
+ if self.hidden:
601
+ self.parameters["hidden"] = "true"
602
+ if self.atlassian_macro_output_type != "BLOCK":
603
+ self.parameters["atlassian-macro-output-type"] = self.atlassian_macro_output_type
604
+
605
+
606
+ @dataclass
607
+ class InfoNode(ExtensionNode):
608
+ """
609
+ Node for Info panel macro.
610
+
611
+ Creates a colored information panel for highlighting important content.
612
+ Content is stored in the children list.
613
+
614
+ Attributes:
615
+ title: Panel title (optional)
616
+ icon: Whether to show icon (default: true)
617
+ """
618
+
619
+ title: Optional[str] = None
620
+ icon: bool = True
621
+
622
+ def __post_init__(self) -> None:
623
+ """Initialize type and extension_key."""
624
+ self.type = "info"
625
+ self.extension_key = "info"
626
+
627
+ # Build parameters dict from attributes
628
+ self.parameters = {}
629
+ if self.title:
630
+ self.parameters["title"] = self.title
631
+ if not self.icon:
632
+ self.parameters["icon"] = "false"
633
+
634
+
635
+ @dataclass
636
+ class WarningNode(ExtensionNode):
637
+ """
638
+ Node for Warning panel macro.
639
+
640
+ Creates a colored warning panel for highlighting cautions.
641
+ Content is stored in the children list.
642
+
643
+ Attributes:
644
+ title: Panel title (optional)
645
+ icon: Whether to show icon (default: true)
646
+ """
647
+
648
+ title: Optional[str] = None
649
+ icon: bool = True
650
+
651
+ def __post_init__(self) -> None:
652
+ """Initialize type and extension_key."""
653
+ self.type = "warning"
654
+ self.extension_key = "warning"
655
+
656
+ # Build parameters dict from attributes
657
+ self.parameters = {}
658
+ if self.title:
659
+ self.parameters["title"] = self.title
660
+ if not self.icon:
661
+ self.parameters["icon"] = "false"
662
+
663
+
664
+ @dataclass
665
+ class NoteNode(ExtensionNode):
666
+ """
667
+ Node for Note panel macro.
668
+
669
+ Creates a colored note panel for additional information.
670
+ Content is stored in the children list.
671
+
672
+ Attributes:
673
+ title: Panel title (optional)
674
+ icon: Whether to show icon (default: true)
675
+ """
676
+
677
+ title: Optional[str] = None
678
+ icon: bool = True
679
+
680
+ def __post_init__(self) -> None:
681
+ """Initialize type and extension_key."""
682
+ self.type = "note"
683
+ self.extension_key = "note"
684
+
685
+ # Build parameters dict from attributes
686
+ self.parameters = {}
687
+ if self.title:
688
+ self.parameters["title"] = self.title
689
+ if not self.icon:
690
+ self.parameters["icon"] = "false"
691
+
692
+
693
+ @dataclass
694
+ class PanelNode(ExtensionNode):
695
+ """
696
+ Node for generic Panel macro.
697
+
698
+ Creates a generic panel (can also be used for tip, success, error panels).
699
+ Content is stored in the children list.
700
+
701
+ Attributes:
702
+ panel_type: Type of panel (info, warning, note, tip, success, error)
703
+ title: Panel title (optional)
704
+ icon: Whether to show icon (default: true)
705
+ """
706
+
707
+ panel_type: str = "info"
708
+ title: Optional[str] = None
709
+ icon: bool = True
710
+
711
+ def __post_init__(self) -> None:
712
+ """Initialize type and extension_key."""
713
+ self.type = "panel"
714
+ self.extension_key = self.panel_type # Use panel_type as extension_key
715
+
716
+ # Build parameters dict from attributes
717
+ self.parameters = {}
718
+ if self.title:
719
+ self.parameters["title"] = self.title
720
+ if not self.icon:
721
+ self.parameters["icon"] = "false"
722
+
723
+
724
+ @dataclass
725
+ class ExpandMacroNode(ExtensionNode):
726
+ """
727
+ Node for Expand macro (collapsible section).
728
+
729
+ Creates a collapsible content section with a title.
730
+ Content is stored in the children list.
731
+
732
+ Attributes:
733
+ title: Title shown in the expand header
734
+ """
735
+
736
+ title: str = ""
737
+
738
+ def __post_init__(self) -> None:
739
+ """Initialize type and extension_key."""
740
+ self.type = "expandMacro"
741
+ self.extension_key = "expand"
742
+
743
+ # Build parameters dict from attributes
744
+ self.parameters = {}
745
+ if self.title:
746
+ self.parameters["title"] = self.title
747
+
748
+
749
+ @dataclass
750
+ class StatusMacroNode(ExtensionNode):
751
+ """
752
+ Node for Status lozenge macro.
753
+
754
+ Creates an inline status badge with color and text.
755
+
756
+ Attributes:
757
+ status_text: Status text to display
758
+ color: Status color (Green, Yellow, Red, Blue, Grey)
759
+ subtle: Use subtle style (default: false)
760
+ """
761
+
762
+ status_text: str = ""
763
+ color: str = "Grey"
764
+ subtle: bool = False
765
+
766
+ def __post_init__(self) -> None:
767
+ """Initialize type and extension_key."""
768
+ self.type = "statusMacro"
769
+ self.extension_key = "status"
770
+
771
+ # Build parameters dict from attributes
772
+ self.parameters = {}
773
+ if self.status_text:
774
+ self.parameters["title"] = self.status_text
775
+ if self.color:
776
+ self.parameters["colour"] = self.color # Confluence uses British spelling
777
+ if self.subtle:
778
+ self.parameters["subtle"] = "true"
779
+
780
+
781
+ @dataclass
782
+ class CodeBlockMacroNode(ExtensionNode):
783
+ """
784
+ Node for enhanced Code Block macro.
785
+
786
+ Creates a code block with enhanced features like title and line numbers.
787
+ Code content is stored in the children list or content attribute.
788
+
789
+ Attributes:
790
+ language: Programming language for syntax highlighting
791
+ title: Code block title
792
+ linenumbers: Show line numbers (default: false)
793
+ theme: Syntax highlighting theme
794
+ collapse: Collapsible code block (default: false)
795
+ """
796
+
797
+ language: Optional[str] = None
798
+ title: Optional[str] = None
799
+ linenumbers: bool = False
800
+ theme: Optional[str] = None
801
+ collapse: bool = False
802
+
803
+ def __post_init__(self) -> None:
804
+ """Initialize type and extension_key."""
805
+ self.type = "codeBlockMacro"
806
+ self.extension_key = "code"
807
+
808
+ # Build parameters dict from attributes
809
+ self.parameters = {}
810
+ if self.language:
811
+ self.parameters["language"] = self.language
812
+ if self.title:
813
+ self.parameters["title"] = self.title
814
+ if self.linenumbers:
815
+ self.parameters["linenumbers"] = "true"
816
+ if self.theme:
817
+ self.parameters["theme"] = self.theme
818
+ if self.collapse:
819
+ self.parameters["collapse"] = "true"