mkdocs2confluence 0.16.0__tar.gz → 0.16.1__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 (110) hide show
  1. {mkdocs2confluence-0.16.0/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.16.1}/PKG-INFO +1 -1
  2. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/pyproject.toml +1 -1
  3. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1/src/mkdocs2confluence.egg-info}/PKG-INFO +1 -1
  4. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/compiler/page.py +4 -2
  5. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/emitter/xhtml.py +3 -3
  6. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/ir/nodes.py +11 -6
  7. mkdocs2confluence-0.16.1/src/mkdocs_to_confluence/parser/__init__.py +5 -0
  8. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/parser/markdown.py +10 -0
  9. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/abbrevs.py +10 -6
  10. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_abbrevs.py +48 -4
  11. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_emitter.py +18 -2
  12. mkdocs2confluence-0.16.0/src/mkdocs_to_confluence/parser/__init__.py +0 -5
  13. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/LICENSE +0 -0
  14. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/README.md +0 -0
  15. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/setup.cfg +0 -0
  16. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs2confluence.egg-info/SOURCES.txt +0 -0
  17. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
  18. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
  19. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
  20. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
  21. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/__init__.py +0 -0
  22. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/cli.py +0 -0
  23. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/compiler/__init__.py +0 -0
  24. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/compiler/models.py +0 -0
  25. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
  26. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
  27. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/ir/document.py +0 -0
  28. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
  29. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
  30. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/loader/config.py +0 -0
  31. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
  32. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/loader/nav.py +0 -0
  33. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/loader/page.py +0 -0
  34. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
  35. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
  36. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/pdf/render.py +0 -0
  37. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
  38. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
  39. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preprocess/captions.py +0 -0
  40. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
  41. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
  42. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
  43. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
  44. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
  45. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
  46. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preview/render.py +0 -0
  47. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/preview/server.py +0 -0
  48. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
  49. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/publisher/changelog.py +0 -0
  50. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/publisher/client.py +0 -0
  51. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/publisher/executor.py +0 -0
  52. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/publisher/http_retry.py +0 -0
  53. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/publisher/models.py +0 -0
  54. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/publisher/pipeline.py +0 -0
  55. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/publisher/planner.py +0 -0
  56. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/skill_installer.py +0 -0
  57. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/skills/mkdocs-changelog/SKILL.md +0 -0
  58. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/skills/mkdocs-changelog/scripts/changelog_data.py +0 -0
  59. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/sync/__init__.py +0 -0
  60. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/sync/anchoring.py +0 -0
  61. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/sync/command.py +0 -0
  62. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/sync/comments.py +0 -0
  63. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/sync/github.py +0 -0
  64. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/sync/platform.py +0 -0
  65. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/sync/state.py +0 -0
  66. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
  67. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/admonition_titles.py +0 -0
  68. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
  69. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/attachment_previews.py +0 -0
  70. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/captions.py +0 -0
  71. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
  72. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/footer.py +0 -0
  73. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/images.py +0 -0
  74. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
  75. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
  76. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_admonition_titles.py +0 -0
  77. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_attachment_previews.py +0 -0
  78. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_captions.py +0 -0
  79. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_changelog_config.py +0 -0
  80. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_changelog_publish.py +0 -0
  81. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_children_macro.py +0 -0
  82. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_cli.py +0 -0
  83. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_editlink.py +0 -0
  84. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_exclude_properties_config.py +0 -0
  85. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_extra_css.py +0 -0
  86. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_footer.py +0 -0
  87. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_frontmatter.py +0 -0
  88. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_icons.py +0 -0
  89. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_images.py +0 -0
  90. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_internallinks.py +0 -0
  91. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_ir.py +0 -0
  92. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_linkdefs.py +0 -0
  93. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_loader.py +0 -0
  94. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_mermaid.py +0 -0
  95. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_page_loader.py +0 -0
  96. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_parser.py +0 -0
  97. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_pdf.py +0 -0
  98. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_preprocess.py +0 -0
  99. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_preview.py +0 -0
  100. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_publish_client.py +0 -0
  101. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_publish_config.py +0 -0
  102. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_publish_pipeline.py +0 -0
  103. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_server.py +0 -0
  104. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_skill_installer.py +0 -0
  105. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_sync_anchoring.py +0 -0
  106. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_sync_command.py +0 -0
  107. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_sync_comments.py +0 -0
  108. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_sync_github.py +0 -0
  109. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_sync_state.py +0 -0
  110. {mkdocs2confluence-0.16.0 → mkdocs2confluence-0.16.1}/tests/test_treeutil.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs2confluence
3
- Version: 0.16.0
3
+ Version: 0.16.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mkdocs2confluence"
3
- version = "0.16.0"
3
+ version = "0.16.1"
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.16.0
3
+ Version: 0.16.1
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
@@ -8,7 +8,7 @@ from mkdocs_to_confluence.ir.nodes import ChildrenMacro, FrontMatter, SourceFoot
8
8
  from mkdocs_to_confluence.loader.config import MkDocsConfig
9
9
  from mkdocs_to_confluence.loader.nav import NavNode
10
10
  from mkdocs_to_confluence.loader.page import load_page
11
- from mkdocs_to_confluence.parser.markdown import parse
11
+ from mkdocs_to_confluence.parser.markdown import parse, parse_inline
12
12
  from mkdocs_to_confluence.preprocess.abbrevs import (
13
13
  extract_abbreviations,
14
14
  strip_abbreviation_defs,
@@ -79,7 +79,9 @@ def compile_page(
79
79
  ir_nodes = strip_links_in_admonition_titles(ir_nodes, node.docs_path or "")
80
80
  if is_section_index:
81
81
  ir_nodes = ir_nodes + (ChildrenMacro(),)
82
- ir_nodes = apply_abbreviations(ir_nodes, abbrevs, page_text=preprocessed)
82
+ # Parse definitions as inline markdown so links etc. survive into the glossary.
83
+ parsed_abbrevs = {abbr: parse_inline(defn) for abbr, defn in abbrevs.items()}
84
+ ir_nodes = apply_abbreviations(ir_nodes, parsed_abbrevs, page_text=preprocessed)
83
85
  ir_nodes, attachments = resolve_local_assets(
84
86
  ir_nodes,
85
87
  page_path=node.source_path,
@@ -623,11 +623,11 @@ def _emit_abbrev_glossary_block(node: AbbrevGlossaryBlock) -> str:
623
623
  f"</ac:structured-macro>"
624
624
  )
625
625
  abbr = html.escape(fn.abbr)
626
- defn = html.escape(fn.definition)
626
+ defn = _emit_inlines(fn.definition)
627
627
  parts.append(f"<li>{anchor_macro}<strong>{abbr}</strong> — {defn}</li>\n")
628
- for abbr, defn in node.extras:
628
+ for fn in node.extras:
629
629
  # No anchor — these only appeared in headings/titles, no inline superscript links here.
630
- parts.append(f"<li><strong>{html.escape(abbr)}</strong> — {html.escape(defn)}</li>\n")
630
+ parts.append(f"<li><strong>{html.escape(fn.abbr)}</strong> — {_emit_inlines(fn.definition)}</li>\n")
631
631
  parts.append("</ol>\n")
632
632
  return "".join(parts)
633
633
 
@@ -497,11 +497,16 @@ class AbbrevFootnoteNode(IRNode):
497
497
 
498
498
  The emitter renders ``ABBR<sup>[N]</sup>`` where ``[N]`` links to the
499
499
  corresponding entry in the end-of-page :class:`AbbrevGlossaryBlock`.
500
+
501
+ ``definition`` holds the definition text parsed as inline IR nodes so
502
+ that links and other inline formatting survive into the glossary.
500
503
  """
501
504
 
502
505
  abbr: str
503
- definition: str
504
- number: int # 1-based, assigned by the transform in order of first encounter
506
+ definition: tuple[IRNode, ...]
507
+ # 1-based, assigned by the transform in order of first encounter.
508
+ # ``None`` for glossary-only entries (no inline superscript, no anchor).
509
+ number: int | None
505
510
 
506
511
 
507
512
  @dataclass(frozen=True)
@@ -509,17 +514,17 @@ class AbbrevGlossaryBlock(IRNode):
509
514
  """End-of-page abbreviations reference block.
510
515
 
511
516
  Rendered as a numbered list (with Confluence anchor targets for the
512
- back-links) followed by an optional bullet list of abbreviations that
513
- only appeared in headings or other non-expandable contexts.
517
+ back-links) followed by entries for abbreviations that only appeared
518
+ in headings or other non-expandable contexts.
514
519
 
515
520
  Attributes:
516
521
  footnoted: Abbreviations annotated inline, ordered by first encounter.
517
- extras: ``(abbr, definition)`` pairs for abbreviations that could
522
+ extras: Entries (``number=None``) for abbreviations that could
518
523
  not be annotated inline, sorted alphabetically.
519
524
  """
520
525
 
521
526
  footnoted: tuple[AbbrevFootnoteNode, ...]
522
- extras: tuple[tuple[str, str], ...]
527
+ extras: tuple[AbbrevFootnoteNode, ...]
523
528
 
524
529
 
525
530
  # ── Footnotes ────────────────────────────────────────────────────────────────
@@ -0,0 +1,5 @@
1
+ """Markdown parser: convert preprocessed markdown text into IR nodes."""
2
+
3
+ from mkdocs_to_confluence.parser.markdown import parse, parse_inline
4
+
5
+ __all__ = ["parse", "parse_inline"]
@@ -111,6 +111,16 @@ def parse(text: str) -> tuple[IRNode, ...]:
111
111
  return _build_tree(tokens)
112
112
 
113
113
 
114
+ def parse_inline(text: str) -> tuple[IRNode, ...]:
115
+ """Parse a standalone snippet of inline markdown into IR inline nodes.
116
+
117
+ Used for one-line fragments that live outside the main document flow,
118
+ e.g. abbreviation definitions, where links and other inline formatting
119
+ must still be honoured.
120
+ """
121
+ return _parse_inline(text)
122
+
123
+
114
124
  # ── Internal token types ──────────────────────────────────────────────────────
115
125
 
116
126
 
@@ -65,7 +65,7 @@ _BLOCK_TYPES = (
65
65
  class _State:
66
66
  """Mutable transform state threaded through the recursive walk."""
67
67
 
68
- def __init__(self, abbrevs: dict[str, str]) -> None:
68
+ def __init__(self, abbrevs: dict[str, tuple[IRNode, ...]]) -> None:
69
69
  self.abbrevs = abbrevs
70
70
  self._expanded_list: list[str] = [] # ordered by first encounter
71
71
  self._expanded_set: set[str] = set() # fast membership test
@@ -207,7 +207,7 @@ def _transform_inline(node: IRNode, state: _State, safe: bool) -> tuple[IRNode,
207
207
  # ── Glossary builder ──────────────────────────────────────────────────────────
208
208
 
209
209
 
210
- def _find_mentioned(text: str, abbrevs: dict[str, str]) -> set[str]:
210
+ def _find_mentioned(text: str, abbrevs: dict[str, tuple[IRNode, ...]]) -> set[str]:
211
211
  """Return abbreviations that appear as whole words anywhere in *text*."""
212
212
  return {
213
213
  abbr
@@ -221,7 +221,7 @@ def _find_mentioned(text: str, abbrevs: dict[str, str]) -> set[str]:
221
221
 
222
222
  def apply_abbreviations(
223
223
  nodes: tuple[IRNode, ...],
224
- abbrevs: dict[str, str],
224
+ abbrevs: dict[str, tuple[IRNode, ...]],
225
225
  *,
226
226
  page_text: str = "",
227
227
  ) -> tuple[IRNode, ...]:
@@ -229,8 +229,12 @@ def apply_abbreviations(
229
229
 
230
230
  Args:
231
231
  nodes: Top-level IR nodes returned by :func:`parse`.
232
- abbrevs: ``{abbreviation: definition}`` mapping, typically from
233
- :func:`~mkdocs_to_confluence.preprocess.abbrevs.extract_abbreviations`.
232
+ abbrevs: ``{abbreviation: definition}`` mapping where each
233
+ definition is parsed inline IR nodes (the caller parses
234
+ the raw definition strings from
235
+ :func:`~mkdocs_to_confluence.preprocess.abbrevs.extract_abbreviations`
236
+ with :func:`~mkdocs_to_confluence.parser.parse_inline` so
237
+ links and other inline formatting are preserved).
234
238
  page_text: The preprocessed page text (after stripping abbreviation
235
239
  definition lines) used to detect which abbreviations are
236
240
  actually present on the page.
@@ -255,7 +259,7 @@ def apply_abbreviations(
255
259
  for i, abbr in enumerate(state._expanded_list)
256
260
  )
257
261
  extras = tuple(
258
- (abbr, abbrevs[abbr])
262
+ AbbrevFootnoteNode(abbr=abbr, definition=abbrevs[abbr], number=None)
259
263
  for abbr in sorted(mentioned - state._expanded_set)
260
264
  )
261
265
 
@@ -8,6 +8,7 @@ from mkdocs_to_confluence.ir.nodes import (
8
8
  Admonition,
9
9
  BoldNode,
10
10
  CodeBlock,
11
+ IRNode,
11
12
  LinkNode,
12
13
  Paragraph,
13
14
  Section,
@@ -16,11 +17,25 @@ from mkdocs_to_confluence.ir.nodes import (
16
17
  TableRow,
17
18
  TextNode,
18
19
  )
20
+ from mkdocs_to_confluence.parser import parse_inline
19
21
  from mkdocs_to_confluence.preprocess.abbrevs import (
20
22
  extract_abbreviations,
21
23
  strip_abbreviation_defs,
22
24
  )
23
- from mkdocs_to_confluence.transforms.abbrevs import apply_abbreviations
25
+ from mkdocs_to_confluence.transforms.abbrevs import (
26
+ apply_abbreviations as _apply_abbreviations,
27
+ )
28
+
29
+
30
+ def apply_abbreviations(
31
+ nodes: tuple[IRNode, ...],
32
+ abbrevs: dict[str, str],
33
+ *,
34
+ page_text: str = "",
35
+ ) -> tuple[IRNode, ...]:
36
+ """Parse string definitions inline (as the compiler does) and apply."""
37
+ parsed = {abbr: parse_inline(defn) for abbr, defn in abbrevs.items()}
38
+ return _apply_abbreviations(nodes, parsed, page_text=page_text)
24
39
 
25
40
  # ── extract_abbreviations ─────────────────────────────────────────────────────
26
41
 
@@ -94,7 +109,7 @@ def test_expands_first_occurrence_as_footnote():
94
109
  fn = children[1]
95
110
  assert isinstance(fn, AbbrevFootnoteNode)
96
111
  assert fn.abbr == "IAM"
97
- assert fn.definition == "Identity and Access Management"
112
+ assert fn.definition == (TextNode("Identity and Access Management"),)
98
113
  assert fn.number == 1
99
114
  # Second occurrence left as plain text
100
115
  assert isinstance(children[2], TextNode)
@@ -141,7 +156,10 @@ def test_glossary_block_appended_for_heading_only_abbrev():
141
156
  glossary = result[1]
142
157
  assert isinstance(glossary, AbbrevGlossaryBlock)
143
158
  assert len(glossary.footnoted) == 0
144
- assert glossary.extras == (("IAM", "Identity and Access Management"),)
159
+ assert len(glossary.extras) == 1
160
+ assert glossary.extras[0].abbr == "IAM"
161
+ assert glossary.extras[0].definition == (TextNode("Identity and Access Management"),)
162
+ assert glossary.extras[0].number is None
145
163
 
146
164
 
147
165
  def test_no_glossary_when_abbrev_footnoted_inline():
@@ -232,7 +250,7 @@ def test_extras_sorted_alphabetically():
232
250
  result = apply_abbreviations((section,), abbrevs, page_text="IAM and RBAC Overview")
233
251
  glossary = result[-1]
234
252
  assert isinstance(glossary, AbbrevGlossaryBlock)
235
- extra_abbrs = [abbr for abbr, _ in glossary.extras]
253
+ extra_abbrs = [fn.abbr for fn in glossary.extras]
236
254
  assert extra_abbrs == sorted(extra_abbrs)
237
255
 
238
256
 
@@ -254,3 +272,29 @@ def test_expands_in_loose_list_item():
254
272
  result = apply_abbreviations(nodes, abbrevs, page_text="Use the API here.")
255
273
  para_children = result[0].items[0].children[0].children # type: ignore[union-attr]
256
274
  assert any(isinstance(c, AbbrevFootnoteNode) and c.abbr == "API" for c in para_children)
275
+
276
+
277
+ # ── Markdown in definitions ──────────────────────────────────────────────────
278
+
279
+
280
+ def test_definition_with_link_parsed_to_link_node():
281
+ abbrevs = {"FB": "The [Foo](http://forr.bar) service"}
282
+ nodes = (_para("Use FB here."),)
283
+ result = apply_abbreviations(nodes, abbrevs, page_text="Use FB here.")
284
+ glossary = result[-1]
285
+ assert isinstance(glossary, AbbrevGlossaryBlock)
286
+ definition = glossary.footnoted[0].definition
287
+ links = [n for n in definition if isinstance(n, LinkNode)]
288
+ assert len(links) == 1
289
+ assert links[0].href == "http://forr.bar"
290
+ assert links[0].children == (TextNode("Foo"),)
291
+
292
+
293
+ def test_extra_definition_with_link_parsed_to_link_node():
294
+ abbrevs = {"FB": "The [Foo](http://forr.bar) service"}
295
+ section = Section(level=2, anchor="fb", title=(TextNode("FB Platform"),), children=())
296
+ result = apply_abbreviations((section,), abbrevs, page_text="FB Platform")
297
+ glossary = result[-1]
298
+ assert isinstance(glossary, AbbrevGlossaryBlock)
299
+ definition = glossary.extras[0].definition
300
+ assert any(isinstance(n, LinkNode) and n.href == "http://forr.bar" for n in definition)
@@ -474,10 +474,10 @@ class TestFootnoteEmitter:
474
474
  """Abbreviations only in headings/titles must appear in <ol>, not <ul>."""
475
475
  from mkdocs_to_confluence.emitter.xhtml import emit
476
476
  from mkdocs_to_confluence.ir.nodes import AbbrevFootnoteNode, AbbrevGlossaryBlock
477
- fn = AbbrevFootnoteNode(abbr="API", definition="Application Programming Interface", number=1)
477
+ fn = AbbrevFootnoteNode(abbr="API", definition=(TextNode("Application Programming Interface"),), number=1)
478
478
  block = AbbrevGlossaryBlock(
479
479
  footnoted=(fn,),
480
- extras=(("AD", "Active Directory"),),
480
+ extras=(AbbrevFootnoteNode(abbr="AD", definition=(TextNode("Active Directory"),), number=None),),
481
481
  )
482
482
  out = emit((block,))
483
483
  assert "<ol>" in out
@@ -488,6 +488,22 @@ class TestFootnoteEmitter:
488
488
  # extras must NOT get an anchor macro
489
489
  assert out.count('ac:name="anchor"') == 1 # only for the footnoted entry
490
490
 
491
+ def test_abbrev_glossary_definition_with_link(self) -> None:
492
+ """A markdown link in a definition must render as an <a>, not raw text."""
493
+ from mkdocs_to_confluence.ir.nodes import AbbrevFootnoteNode, AbbrevGlossaryBlock
494
+ defn = (
495
+ TextNode("The "),
496
+ LinkNode(href="http://forr.bar", children=(TextNode("Foo"),)),
497
+ TextNode(" service"),
498
+ )
499
+ block = AbbrevGlossaryBlock(
500
+ footnoted=(AbbrevFootnoteNode(abbr="FB", definition=defn, number=1),),
501
+ extras=(AbbrevFootnoteNode(abbr="BZ", definition=defn, number=None),),
502
+ )
503
+ out = emit((block,))
504
+ assert out.count('<a href="http://forr.bar">Foo</a>') == 2
505
+ assert "[Foo]" not in out
506
+
491
507
 
492
508
  # ── Inline HTML emitters ──────────────────────────────────────────────────────
493
509
 
@@ -1,5 +0,0 @@
1
- """Markdown parser: convert preprocessed markdown text into IR nodes."""
2
-
3
- from mkdocs_to_confluence.parser.markdown import parse
4
-
5
- __all__ = ["parse"]