mkdocs2confluence 0.6.5__tar.gz → 0.6.7__tar.gz

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 (66) hide show
  1. {mkdocs2confluence-0.6.5/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.6.7}/PKG-INFO +9 -3
  2. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/README.md +8 -2
  3. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/pyproject.toml +1 -1
  4. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7/src/mkdocs2confluence.egg-info}/PKG-INFO +9 -3
  5. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/emitter/xhtml.py +3 -0
  6. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/ir/__init__.py +2 -0
  7. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/ir/nodes.py +7 -0
  8. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/loader/config.py +7 -2
  9. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/parser/markdown.py +58 -16
  10. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_emitter.py +5 -0
  11. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_ir.py +5 -0
  12. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_parser.py +32 -0
  13. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_publish_pipeline.py +1 -1
  14. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/LICENSE +0 -0
  15. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/setup.cfg +0 -0
  16. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs2confluence.egg-info/SOURCES.txt +0 -0
  17. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
  18. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
  19. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
  20. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
  21. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/__init__.py +0 -0
  22. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/cli.py +0 -0
  23. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
  24. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/ir/document.py +0 -0
  25. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
  26. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
  27. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
  28. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/loader/nav.py +0 -0
  29. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/loader/page.py +0 -0
  30. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
  31. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
  32. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
  33. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
  34. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
  35. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
  36. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
  37. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
  38. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
  39. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/preview/render.py +0 -0
  40. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
  41. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/publisher/client.py +0 -0
  42. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/publisher/pipeline.py +0 -0
  43. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
  44. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
  45. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
  46. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
  47. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/transforms/images.py +0 -0
  48. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
  49. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
  50. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_abbrevs.py +0 -0
  51. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_cli.py +0 -0
  52. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_editlink.py +0 -0
  53. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_extra_css.py +0 -0
  54. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_frontmatter.py +0 -0
  55. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_icons.py +0 -0
  56. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_images.py +0 -0
  57. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_internallinks.py +0 -0
  58. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_linkdefs.py +0 -0
  59. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_loader.py +0 -0
  60. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_mermaid.py +0 -0
  61. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_page_loader.py +0 -0
  62. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_preprocess.py +0 -0
  63. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_preview.py +0 -0
  64. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_publish_client.py +0 -0
  65. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_publish_config.py +0 -0
  66. {mkdocs2confluence-0.6.5 → mkdocs2confluence-0.6.7}/tests/test_treeutil.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs2confluence
3
- Version: 0.6.5
3
+ Version: 0.6.7
4
4
  Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
5
5
  Author: Anders Hybertz
6
6
  License: GPL-3.0-or-later
@@ -36,7 +36,7 @@ Requires-Dist: bandit; extra == "dev"
36
36
  Requires-Dist: build; extra == "dev"
37
37
  Dynamic: license-file
38
38
 
39
- # mk2conf — MkDocs to Confluence
39
+ # mk2conf — MkDocs / Zensical to Confluence
40
40
 
41
41
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
42
42
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
@@ -54,6 +54,8 @@ A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence
54
54
 
55
55
  It is a **compiler/transpiler**, not an HTML converter — every Markdown construct is mapped to the equivalent native Confluence macro or element, so pages look and behave like hand-authored Confluence content.
56
56
 
57
+ > **Zensical compatible** — [Zensical](https://zensical.org/) is the modern successor to MkDocs + Material for MkDocs, built by the same team. Since Zensical uses the same `mkdocs.yml` format and Python Markdown extensions, your Zensical project works with this tool today — no changes required.
58
+
57
59
  ---
58
60
 
59
61
  ## Architecture
@@ -255,12 +257,16 @@ extra_css:
255
257
  | `**bold**` / `__bold__` | `<strong>` |
256
258
  | `*italic*` | `<em>` |
257
259
  | `~~strikethrough~~` | `<s>` |
260
+ | `~subscript~` | `<sub>` (pymdownx.tilde) |
261
+ | `^superscript^` | `<sup>` (pymdownx.caret) |
262
+ | `^^inserted^^` | `<u>` (pymdownx.caret insert) |
258
263
  | `` `inline code` `` | `<code>` |
259
264
  | `[text](url)` | `<a href="...">` |
265
+ | `https://bare-url` | `<a href="...">` (autolink) |
260
266
  | `[text](file.pdf)` | `<ac:link><ri:attachment .../>` (uploaded as attachment) |
261
267
  | `![alt](src)` | `<ac:image>` with `<ri:attachment>` (local) or `<ri:url>` (remote) |
262
268
  | `![alt](src){ width="400" }` | `<ac:image ac:width="400">` — also supports `height` and `align` |
263
- | `<br>` / `<br/>` | `<br />` |
269
+ | `<br>` / `<br/>` / trailing `\` | `<br />` |
264
270
  | `<sub>` / `<sup>` / `<u>` / `<small>` | Direct XHTML passthrough |
265
271
  | `<mark>text</mark>` | `<span style="background-color: yellow;">` |
266
272
  | `<kbd>text</kbd>` | `<code>` |
@@ -1,4 +1,4 @@
1
- # mk2conf — MkDocs to Confluence
1
+ # mk2conf — MkDocs / Zensical to Confluence
2
2
 
3
3
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
4
4
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
@@ -16,6 +16,8 @@ A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence
16
16
 
17
17
  It is a **compiler/transpiler**, not an HTML converter — every Markdown construct is mapped to the equivalent native Confluence macro or element, so pages look and behave like hand-authored Confluence content.
18
18
 
19
+ > **Zensical compatible** — [Zensical](https://zensical.org/) is the modern successor to MkDocs + Material for MkDocs, built by the same team. Since Zensical uses the same `mkdocs.yml` format and Python Markdown extensions, your Zensical project works with this tool today — no changes required.
20
+
19
21
  ---
20
22
 
21
23
  ## Architecture
@@ -217,12 +219,16 @@ extra_css:
217
219
  | `**bold**` / `__bold__` | `<strong>` |
218
220
  | `*italic*` | `<em>` |
219
221
  | `~~strikethrough~~` | `<s>` |
222
+ | `~subscript~` | `<sub>` (pymdownx.tilde) |
223
+ | `^superscript^` | `<sup>` (pymdownx.caret) |
224
+ | `^^inserted^^` | `<u>` (pymdownx.caret insert) |
220
225
  | `` `inline code` `` | `<code>` |
221
226
  | `[text](url)` | `<a href="...">` |
227
+ | `https://bare-url` | `<a href="...">` (autolink) |
222
228
  | `[text](file.pdf)` | `<ac:link><ri:attachment .../>` (uploaded as attachment) |
223
229
  | `![alt](src)` | `<ac:image>` with `<ri:attachment>` (local) or `<ri:url>` (remote) |
224
230
  | `![alt](src){ width="400" }` | `<ac:image ac:width="400">` — also supports `height` and `align` |
225
- | `<br>` / `<br/>` | `<br />` |
231
+ | `<br>` / `<br/>` / trailing `\` | `<br />` |
226
232
  | `<sub>` / `<sup>` / `<u>` / `<small>` | Direct XHTML passthrough |
227
233
  | `<mark>text</mark>` | `<span style="background-color: yellow;">` |
228
234
  | `<kbd>text</kbd>` | `<code>` |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mkdocs2confluence"
3
- version = "0.6.5"
3
+ version = "0.6.7"
4
4
  description = "Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more"
5
5
  readme = "README.md"
6
6
  license = { text = "GPL-3.0-or-later" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs2confluence
3
- Version: 0.6.5
3
+ Version: 0.6.7
4
4
  Summary: Publish MkDocs Material pages to Confluence Cloud — admonitions, Mermaid diagrams, tabs, page properties and more
5
5
  Author: Anders Hybertz
6
6
  License: GPL-3.0-or-later
@@ -36,7 +36,7 @@ Requires-Dist: bandit; extra == "dev"
36
36
  Requires-Dist: build; extra == "dev"
37
37
  Dynamic: license-file
38
38
 
39
- # mk2conf — MkDocs to Confluence
39
+ # mk2conf — MkDocs / Zensical to Confluence
40
40
 
41
41
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
42
42
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
@@ -54,6 +54,8 @@ A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence
54
54
 
55
55
  It is a **compiler/transpiler**, not an HTML converter — every Markdown construct is mapped to the equivalent native Confluence macro or element, so pages look and behave like hand-authored Confluence content.
56
56
 
57
+ > **Zensical compatible** — [Zensical](https://zensical.org/) is the modern successor to MkDocs + Material for MkDocs, built by the same team. Since Zensical uses the same `mkdocs.yml` format and Python Markdown extensions, your Zensical project works with this tool today — no changes required.
58
+
57
59
  ---
58
60
 
59
61
  ## Architecture
@@ -255,12 +257,16 @@ extra_css:
255
257
  | `**bold**` / `__bold__` | `<strong>` |
256
258
  | `*italic*` | `<em>` |
257
259
  | `~~strikethrough~~` | `<s>` |
260
+ | `~subscript~` | `<sub>` (pymdownx.tilde) |
261
+ | `^superscript^` | `<sup>` (pymdownx.caret) |
262
+ | `^^inserted^^` | `<u>` (pymdownx.caret insert) |
258
263
  | `` `inline code` `` | `<code>` |
259
264
  | `[text](url)` | `<a href="...">` |
265
+ | `https://bare-url` | `<a href="...">` (autolink) |
260
266
  | `[text](file.pdf)` | `<ac:link><ri:attachment .../>` (uploaded as attachment) |
261
267
  | `![alt](src)` | `<ac:image>` with `<ri:attachment>` (local) or `<ri:url>` (remote) |
262
268
  | `![alt](src){ width="400" }` | `<ac:image ac:width="400">` — also supports `height` and `align` |
263
- | `<br>` / `<br/>` | `<br />` |
269
+ | `<br>` / `<br/>` / trailing `\` | `<br />` |
264
270
  | `<sub>` / `<sup>` / `<u>` / `<small>` | Direct XHTML passthrough |
265
271
  | `<mark>text</mark>` | `<span style="background-color: yellow;">` |
266
272
  | `<kbd>text</kbd>` | `<code>` |
@@ -37,6 +37,7 @@ from mkdocs_to_confluence.ir.nodes import (
37
37
  HorizontalRule,
38
38
  ImageNode,
39
39
  InlineHtmlNode,
40
+ InsertNode,
40
41
  IRNode,
41
42
  ItalicNode,
42
43
  LineBreakNode,
@@ -548,6 +549,8 @@ def _emit_inline(node: IRNode) -> str:
548
549
  return f"<sub>{_emit_inlines(node.children)}</sub>"
549
550
  if isinstance(node, SuperscriptNode):
550
551
  return f"<sup>{_emit_inlines(node.children)}</sup>"
552
+ if isinstance(node, InsertNode):
553
+ return f"<u>{_emit_inlines(node.children)}</u>"
551
554
  if isinstance(node, CodeInlineNode):
552
555
  style_attr = styles_to_attr(_styles.code_inline) if _styles else ""
553
556
  return f"<code{style_attr}>{html.escape(node.code)}</code>"
@@ -32,6 +32,7 @@ from mkdocs_to_confluence.ir.nodes import (
32
32
  ImageNode,
33
33
  # Inline HTML nodes
34
34
  InlineHtmlNode,
35
+ InsertNode,
35
36
  # Traversal utility
36
37
  IRNode,
37
38
  ItalicNode,
@@ -79,6 +80,7 @@ __all__ = [
79
80
  "ImageNode",
80
81
  "LineBreakNode",
81
82
  "InlineHtmlNode",
83
+ "InsertNode",
82
84
  # Block
83
85
  "Section",
84
86
  "Paragraph",
@@ -73,6 +73,13 @@ class SubscriptNode(IRNode):
73
73
  children: tuple[IRNode, ...]
74
74
 
75
75
 
76
+ @dataclass(frozen=True)
77
+ class InsertNode(IRNode):
78
+ """Inserted (underlined) inline content (``^^text^^``)."""
79
+
80
+ children: tuple[IRNode, ...]
81
+
82
+
76
83
  @dataclass(frozen=True)
77
84
  class CodeInlineNode(IRNode):
78
85
  """An inline code span (`` `code` ``)."""
@@ -7,6 +7,7 @@ import re
7
7
  from dataclasses import dataclass
8
8
  from pathlib import Path
9
9
  from typing import Any
10
+ from urllib.parse import urlparse
10
11
 
11
12
  import yaml
12
13
 
@@ -118,9 +119,13 @@ def _default_edit_uri(repo_url: str | None) -> str | None:
118
119
  """Return a sensible default ``edit_uri`` based on the hosting platform."""
119
120
  if not repo_url:
120
121
  return None
121
- if "github.com" in repo_url:
122
+ try:
123
+ hostname = urlparse(repo_url).hostname or ""
124
+ except ValueError:
125
+ return None
126
+ if hostname == "github.com" or hostname.endswith(".github.com"):
122
127
  return "edit/main/docs/"
123
- if "gitlab.com" in repo_url or "gitlab." in repo_url:
128
+ if hostname == "gitlab.com" or hostname.endswith(".gitlab.com") or "gitlab" in hostname:
124
129
  return "-/edit/master/docs/"
125
130
  return None
126
131
 
@@ -1,24 +1,36 @@
1
- """Minimal markdown-to-IR parser.
2
-
3
- Supported in this milestone
1
+ """Markdown-to-IR parser.
2
+
3
+ Supported inline constructs
4
+ ----------------------------
5
+ * ``**bold**`` / ``__bold__`` → :class:`~ir.BoldNode`
6
+ * ``*italic*`` → :class:`~ir.ItalicNode`
7
+ * ``~~strikethrough~~`` → :class:`~ir.StrikethroughNode`
8
+ * ``~subscript~`` → :class:`~ir.SubscriptNode`
9
+ * ``^superscript^`` → :class:`~ir.SuperscriptNode`
10
+ * ``^^insert^^`` (underline) → :class:`~ir.InsertNode`
11
+ * `` `code` `` → :class:`~ir.CodeInlineNode`
12
+ * ``[text](url)`` → :class:`~ir.LinkNode`
13
+ * Bare ``https://`` / ``http://`` URLs → :class:`~ir.LinkNode`
14
+ * ``![alt](src)`` → :class:`~ir.ImageNode`
15
+ * ``<br>`` / ``<br/>`` / trailing ``\\`` → :class:`~ir.LineBreakNode`
16
+ * ``<mark>``, ``<kbd>``, ``<sub>``, ``<sup>``, ``<u>``, ``<s>``, ``<small>`` → :class:`~ir.InlineHtmlNode`
17
+ * ``++key+key++`` → :class:`~ir.InlineHtmlNode` ``kbd`` (pymdownx.keys)
18
+ * ``[^label]`` footnote references → :class:`~ir.FootnoteRef`
19
+
20
+ Supported block constructs
4
21
  ---------------------------
5
22
  * ATX headings (``# H1`` … ``###### H6``) → :class:`~ir.Section`
6
23
  * Fenced code blocks (`` ``` `` or ``~~~``) → :class:`~ir.CodeBlock`
7
24
  with full Material attribute parsing (language, title, linenums, hl_lines)
8
25
  * Paragraphs (consecutive non-blank lines) → :class:`~ir.Paragraph`
9
26
  * Admonitions (``!!!``/``???``/``???+``) → :class:`~ir.Admonition`
10
- Body is recursively tokenized, so nested code blocks and paragraphs work.
11
-
12
- Not yet supported (later milestones)
13
- --------------------------------------
14
- * Inline formatting (bold, italic, links, images)
15
- * Content tabs, mermaid diagrams
16
- * Setext headings (underline style)
17
- * Block quotes, lists, tables, horizontal rules
18
-
19
- Inline content of headings and paragraphs is represented as a single
20
- :class:`~ir.TextNode` carrying the raw text; once the inline parser is
21
- implemented that node will be replaced by structured inline nodes.
27
+ * Content tabs (``=== "Label"``) :class:`~ir.ContentTabs`
28
+ * Ordered and unordered lists, incl. task lists (``- [x]``) → :class:`~ir.BulletList` / :class:`~ir.OrderedList`
29
+ * Tables :class:`~ir.Table`
30
+ * Definition lists → :class:`~ir.DefinitionList`
31
+ * Block quotes :class:`~ir.BlockQuote`
32
+ * Horizontal rules :class:`~ir.HorizontalRule`
33
+ * Mermaid fenced blocks → :class:`~ir.MermaidDiagram`
22
34
 
23
35
  Architecture notes
24
36
  ------------------
@@ -57,6 +69,7 @@ from mkdocs_to_confluence.ir.nodes import (
57
69
  HorizontalRule,
58
70
  ImageNode,
59
71
  InlineHtmlNode,
72
+ InsertNode,
60
73
  IRNode,
61
74
  ItalicNode,
62
75
  LineBreakNode,
@@ -833,6 +846,16 @@ def _scan_inline(text: str, fn_map: dict[str, int] | None = None) -> list[IRNode
833
846
  i = close_idx + 1
834
847
  continue
835
848
 
849
+ # Insert (underline): ^^text^^ — checked before single ^
850
+ if text[i : i + 2] == "^^":
851
+ close_idx = text.find("^^", i + 2)
852
+ if close_idx != -1:
853
+ flush()
854
+ inner = _scan_inline(text[i + 2 : close_idx], _fn)
855
+ nodes.append(InsertNode(children=tuple(inner)))
856
+ i = close_idx + 2
857
+ continue
858
+
836
859
  # Superscript: ^text^
837
860
  if text[i] == "^":
838
861
  close_idx = text.find("^", i + 1)
@@ -878,6 +901,16 @@ def _scan_inline(text: str, fn_map: dict[str, int] | None = None) -> list[IRNode
878
901
  i = close_idx + len(close)
879
902
  continue
880
903
 
904
+ # Bare URL autolink: https:// or http://
905
+ if text[i : i + 4] in ("http", "ftp:"):
906
+ url_m = re.match(r"(https?://|ftp://)[^\s<>\[\]\"']+", text[i:])
907
+ if url_m:
908
+ flush()
909
+ url = url_m.group(0)
910
+ nodes.append(LinkNode(href=url, children=(TextNode(text=url),)))
911
+ i += len(url)
912
+ continue
913
+
881
914
  buf += text[i]
882
915
  i += 1
883
916
 
@@ -1067,7 +1100,16 @@ def _append_content(
1067
1100
 
1068
1101
  def _paragraph_node(token: _ParagraphToken, fn_map: dict[str, int] | None = None) -> Paragraph:
1069
1102
  """Convert a paragraph token into a :class:`~ir.Paragraph` node."""
1070
- text = " ".join(line.strip() for line in token.lines)
1103
+ # Trailing backslash on a line is a hard line break (CommonMark / pymdownx.escapeall).
1104
+ # Preserve it as <br> so the inline parser can detect it.
1105
+ parts: list[str] = []
1106
+ for line in token.lines:
1107
+ stripped = line.strip()
1108
+ if stripped.endswith("\\"):
1109
+ parts.append(stripped[:-1] + "<br>")
1110
+ else:
1111
+ parts.append(stripped)
1112
+ text = " ".join(parts)
1071
1113
  return Paragraph(children=_parse_inline(text, fn_map=fn_map))
1072
1114
 
1073
1115
 
@@ -12,6 +12,7 @@ from mkdocs_to_confluence.ir.nodes import (
12
12
  ContentTabs,
13
13
  Expandable,
14
14
  HorizontalRule,
15
+ InsertNode,
15
16
  ItalicNode,
16
17
  LinkNode,
17
18
  ListItem,
@@ -267,6 +268,10 @@ class TestSubscriptSuperscriptEmitter:
267
268
  out = emit((Paragraph((SuperscriptNode((TextNode("2"),)),)),))
268
269
  assert "<sup>2</sup>" in out
269
270
 
271
+ def test_insert(self) -> None:
272
+ out = emit((Paragraph((InsertNode((TextNode("new"),)),)),))
273
+ assert "<u>new</u>" in out
274
+
270
275
 
271
276
  class TestDefinitionListEmitter:
272
277
  def test_basic_dl(self) -> None:
@@ -19,6 +19,7 @@ from mkdocs_to_confluence.ir import (
19
19
  Expandable,
20
20
  HorizontalRule,
21
21
  ImageNode,
22
+ InsertNode,
22
23
  IRNode,
23
24
  ItalicNode,
24
25
  LinkNode,
@@ -192,6 +193,10 @@ class TestInlineNodes:
192
193
  node = SuperscriptNode(children=(TextNode(text="2"),))
193
194
  assert len(node.children) == 1
194
195
 
196
+ def test_insert_node(self) -> None:
197
+ node = InsertNode(children=(TextNode(text="new"),))
198
+ assert len(node.children) == 1
199
+
195
200
  def test_code_inline_node(self) -> None:
196
201
  node = CodeInlineNode(code="print()")
197
202
  assert node.code == "print()"
@@ -735,6 +735,7 @@ from mkdocs_to_confluence.ir import ( # noqa: E402
735
735
  DefinitionList,
736
736
  HorizontalRule,
737
737
  ImageNode,
738
+ InsertNode,
738
739
  ItalicNode,
739
740
  LinkNode,
740
741
  OrderedList,
@@ -783,6 +784,37 @@ class TestInlineParsing:
783
784
  sup = next(n for n in para.children if isinstance(n, SuperscriptNode))
784
785
  assert sup.children[0].text == "2" # type: ignore[union-attr]
785
786
 
787
+ def test_insert_underline(self) -> None:
788
+ para = first(parse("^^inserted^^\n"), Paragraph)
789
+ assert isinstance(para, Paragraph)
790
+ ins = next(n for n in para.children if isinstance(n, InsertNode))
791
+ assert ins.children[0].text == "inserted" # type: ignore[union-attr]
792
+
793
+ def test_insert_does_not_consume_superscript(self) -> None:
794
+ """^^text^^ should produce InsertNode, not two SuperscriptNodes."""
795
+ para = first(parse("^^hello^^\n"), Paragraph)
796
+ nodes = [n for n in para.children if isinstance(n, InsertNode)]
797
+ sup_nodes = [n for n in para.children if isinstance(n, SuperscriptNode)]
798
+ assert len(nodes) == 1
799
+ assert len(sup_nodes) == 0
800
+
801
+ def test_bare_url_autolink(self) -> None:
802
+ para = first(parse("See https://example.com for details.\n"), Paragraph)
803
+ link = next(n for n in para.children if isinstance(n, LinkNode))
804
+ assert link.href == "https://example.com"
805
+ assert link.children[0].text == "https://example.com" # type: ignore[union-attr]
806
+
807
+ def test_bare_url_http(self) -> None:
808
+ para = first(parse("Visit http://example.com today.\n"), Paragraph)
809
+ link = next(n for n in para.children if isinstance(n, LinkNode))
810
+ assert link.href == "http://example.com"
811
+
812
+ def test_trailing_backslash_hard_break(self) -> None:
813
+ from mkdocs_to_confluence.ir import LineBreakNode
814
+ nodes = parse("Line one\\\nLine two\n")
815
+ para = first(nodes, Paragraph)
816
+ assert any(isinstance(n, LineBreakNode) for n in para.children)
817
+
786
818
  def test_inline_code(self) -> None:
787
819
  para = first(parse("Use `foo()` here.\n"), Paragraph)
788
820
  assert isinstance(para, Paragraph)
@@ -332,7 +332,7 @@ def test_dry_run_prints_page_list(tmp_path: Path, capsys: pytest.CaptureFixture)
332
332
 
333
333
  captured = capsys.readouterr()
334
334
  assert "Dry run" in captured.out
335
- assert "example.atlassian.net" in captured.out
335
+ assert "example.atlassian.net" in captured.out # codeql[py/incomplete-url-substring-sanitization]
336
336
 
337
337
 
338
338
  def test_dry_run_no_api_calls(tmp_path: Path, capsys: pytest.CaptureFixture) -> None: