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,5 @@
1
+ """
2
+ Markdown extensions module.
3
+
4
+ This module provides extensions for the Markdown parser.
5
+ """
@@ -0,0 +1,80 @@
1
+ """
2
+ Frontmatter parser extension.
3
+ """
4
+
5
+ import re
6
+ from typing import Any, Dict, Optional, Tuple
7
+
8
+
9
+ class FrontmatterParser:
10
+ """
11
+ Parser for YAML frontmatter in Markdown files.
12
+ """
13
+
14
+ @staticmethod
15
+ def extract(content: str) -> Tuple[Dict[str, Any], str]:
16
+ """
17
+ Extract YAML frontmatter from content.
18
+
19
+ Args:
20
+ content: Markdown content with frontmatter
21
+
22
+ Returns:
23
+ Tuple of (frontmatter dict, content without frontmatter)
24
+ """
25
+ frontmatter_pattern = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
26
+ match = frontmatter_pattern.match(content)
27
+
28
+ if match:
29
+ try:
30
+ import yaml
31
+
32
+ frontmatter = yaml.safe_load(match.group(1)) or {}
33
+ # Remove frontmatter from content
34
+ remaining_content = content[match.end() :]
35
+ return frontmatter, remaining_content
36
+ except (yaml.YAMLError, ImportError):
37
+ # Invalid YAML or yaml module not available
38
+ pass
39
+
40
+ return {}, content
41
+
42
+ @staticmethod
43
+ def get_title(frontmatter: Dict[str, Any], default: Optional[str] = None) -> Optional[str]:
44
+ """
45
+ Get title from frontmatter.
46
+
47
+ Args:
48
+ frontmatter: Frontmatter dictionary
49
+ default: Default value if title not found
50
+
51
+ Returns:
52
+ Title or default value
53
+ """
54
+ return frontmatter.get("connie-title", frontmatter.get("title", default))
55
+
56
+ @staticmethod
57
+ def get_page_id(frontmatter: Dict[str, Any]) -> Optional[str]:
58
+ """
59
+ Get Confluence page ID from frontmatter.
60
+
61
+ Args:
62
+ frontmatter: Frontmatter dictionary
63
+
64
+ Returns:
65
+ Page ID or None
66
+ """
67
+ return frontmatter.get("connie-page-id")
68
+
69
+ @staticmethod
70
+ def should_publish(frontmatter: Dict[str, Any]) -> bool:
71
+ """
72
+ Check if page should be published based on frontmatter.
73
+
74
+ Args:
75
+ frontmatter: Frontmatter dictionary
76
+
77
+ Returns:
78
+ True if page should be published, False otherwise
79
+ """
80
+ return frontmatter.get("connie-publish", True)
@@ -0,0 +1,64 @@
1
+ """
2
+ Mermaid diagram parser extension.
3
+ """
4
+
5
+ import re
6
+ import uuid
7
+ from typing import List, Optional, Tuple
8
+
9
+ from docspan.backends.confluence.markdown.ast import MarkdownNode, MermaidNode
10
+
11
+
12
+ class MermaidParser:
13
+ """
14
+ Parser for Mermaid diagram code blocks.
15
+ """
16
+
17
+ @staticmethod
18
+ def parse(content: str) -> List[Tuple[str, MarkdownNode]]:
19
+ """
20
+ Parse Mermaid diagrams in content and return list of replacements.
21
+
22
+ Args:
23
+ content: Markdown content to parse
24
+
25
+ Returns:
26
+ List of tuples (original text, MermaidNode)
27
+ """
28
+ pattern = r"```mermaid\n([\s\S]+?)\n```"
29
+ matches = re.finditer(pattern, content)
30
+
31
+ replacements = []
32
+ for match in matches:
33
+ original = match.group(0)
34
+ code = match.group(1).strip()
35
+
36
+ node = MermaidNode(code=code)
37
+ node.attrs["id"] = str(uuid.uuid4())
38
+
39
+ replacements.append((original, node))
40
+
41
+ return replacements
42
+
43
+ @staticmethod
44
+ def render_diagram(code: str, output_path: Optional[str] = None) -> Optional[str]:
45
+ """
46
+ Render a Mermaid diagram to an image file.
47
+
48
+ Args:
49
+ code: Mermaid diagram code
50
+ output_path: Path to save the rendered image (generated if None)
51
+
52
+ Returns:
53
+ Path to the rendered image or None if rendering failed
54
+
55
+ Note:
56
+ This is a placeholder. Implementation would typically use a library like
57
+ node-mermaid or puppeteer to render the diagram.
58
+ """
59
+ # TODO: Implement actual rendering logic
60
+ # For now, just return a dummy path
61
+ if not output_path:
62
+ output_path = f"diagram_{uuid.uuid4()}.png"
63
+
64
+ return output_path
@@ -0,0 +1,179 @@
1
+ """
2
+ Wikilinks parser extension.
3
+ """
4
+
5
+ import re
6
+ from typing import Any, Dict, List, Optional, Tuple
7
+
8
+ from docspan.backends.confluence.markdown.ast import LinkNode, MarkdownNode, TextNode, WikiLinkNode
9
+
10
+
11
+ class WikilinksParser:
12
+ """
13
+ Parser for wiki-style links ([[page]] or [[page|title]]).
14
+ """
15
+
16
+ PATTERN = r"\[\[([^\]|]+)(?:\|([^\]]+))?\]\]"
17
+
18
+ @classmethod
19
+ def parse(cls, content: str) -> List[Tuple[str, MarkdownNode]]:
20
+ """
21
+ Parse wiki links in content and return list of replacements.
22
+
23
+ Args:
24
+ content: Text content to parse
25
+
26
+ Returns:
27
+ List of tuples (original text, WikiLinkNode)
28
+ """
29
+ pattern = cls.PATTERN
30
+ matches = re.finditer(pattern, content)
31
+
32
+ replacements = []
33
+ for match in matches:
34
+ original = match.group(0)
35
+ target = match.group(1).strip()
36
+ display = match.group(2).strip() if match.group(2) else None
37
+
38
+ node = WikiLinkNode(target=target, display=display)
39
+ replacements.append((original, node))
40
+
41
+ return replacements
42
+
43
+ @classmethod
44
+ def replace_wikilinks(
45
+ cls, text_node: TextNode, page_map: Dict[str, Any] = None
46
+ ) -> List[MarkdownNode]:
47
+ """
48
+ Replace wiki links in a text node with link nodes.
49
+
50
+ Args:
51
+ text_node: Text node to process
52
+ page_map: Optional mapping of page names to page IDs
53
+
54
+ Returns:
55
+ List of nodes with wiki links replaced
56
+ """
57
+ if not text_node.content:
58
+ return [text_node]
59
+
60
+ content = text_node.content
61
+ pattern = cls.PATTERN
62
+
63
+ # Find all wikilink matches
64
+ matches = list(re.finditer(pattern, content))
65
+ if not matches:
66
+ return [text_node]
67
+
68
+ # Create result nodes
69
+ result = []
70
+ last_end = 0
71
+
72
+ for match in matches:
73
+ start, end = match.span()
74
+
75
+ # Add text before the link
76
+ if start > last_end:
77
+ result.append(TextNode(content=content[last_end:start]))
78
+
79
+ # Process the link
80
+ target = match.group(1).strip()
81
+ display = match.group(2).strip() if match.group(2) else target
82
+
83
+ # Resolve link target if page map provided
84
+ page_id = None
85
+ if page_map:
86
+ page_id = cls.resolve_link(target, page_map)
87
+
88
+ # Create a wiki link node or standard link node
89
+ if page_id:
90
+ # Create regular link to Confluence page
91
+ result.append(LinkNode(url=f"#page-{page_id}", title=target))
92
+ link_node = result[-1]
93
+ link_node.children.append(TextNode(content=display))
94
+ else:
95
+ # Create wiki link node
96
+ result.append(WikiLinkNode(target=target, display=display))
97
+
98
+ last_end = end
99
+
100
+ # Add remaining text
101
+ if last_end < len(content):
102
+ result.append(TextNode(content=content[last_end:]))
103
+
104
+ return result
105
+
106
+ @classmethod
107
+ def resolve_link(cls, target: str, page_map: Dict[str, Any]) -> Optional[str]:
108
+ """
109
+ Resolve wiki link target to a Confluence page ID.
110
+
111
+ Args:
112
+ target: Link target (page name)
113
+ page_map: Dictionary mapping page names to page IDs
114
+
115
+ Returns:
116
+ Page ID or None if not found
117
+ """
118
+ # Check direct match
119
+ if target in page_map:
120
+ return page_map[target]
121
+
122
+ # Try case-insensitive match
123
+ target_lower = target.lower()
124
+ for name, page_id in page_map.items():
125
+ if name.lower() == target_lower:
126
+ return page_id
127
+
128
+ # Try with different separators
129
+ normalized_target = cls._normalize_page_name(target)
130
+ for name, page_id in page_map.items():
131
+ normalized_name = cls._normalize_page_name(name)
132
+ if normalized_name == normalized_target:
133
+ return page_id
134
+
135
+ return None
136
+
137
+ @staticmethod
138
+ def _normalize_page_name(name: str) -> str:
139
+ """
140
+ Normalize a page name for comparison.
141
+
142
+ Args:
143
+ name: Page name to normalize
144
+
145
+ Returns:
146
+ Normalized page name
147
+ """
148
+ # Replace spaces, hyphens, and underscores with a common separator
149
+ result = re.sub(r"[ \-_]+", "-", name.lower())
150
+ # Remove any non-alphanumeric chars (except dashes)
151
+ result = re.sub(r"[^\w\-]", "", result, flags=re.ASCII)
152
+ return result
153
+
154
+ @classmethod
155
+ def build_page_map(cls, pages: List[Dict[str, Any]]) -> Dict[str, str]:
156
+ """
157
+ Build a mapping of page titles to page IDs.
158
+
159
+ Args:
160
+ pages: List of page data dictionaries
161
+
162
+ Returns:
163
+ Dictionary mapping page titles to page IDs
164
+ """
165
+ result = {}
166
+
167
+ for page in pages:
168
+ title = page.get("title")
169
+ page_id = page.get("id")
170
+
171
+ if title and page_id:
172
+ result[title] = page_id
173
+
174
+ # Also add normalized version
175
+ normalized = cls._normalize_page_name(title)
176
+ if normalized != title.lower():
177
+ result[normalized] = page_id
178
+
179
+ return result