mkdocs2confluence 0.7.10__tar.gz → 0.7.12__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.
- {mkdocs2confluence-0.7.10/src/mkdocs2confluence.egg-info → mkdocs2confluence-0.7.12}/PKG-INFO +1 -1
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/pyproject.toml +1 -1
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12/src/mkdocs2confluence.egg-info}/PKG-INFO +1 -1
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/emitter/xhtml.py +31 -5
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/ir/nodes.py +22 -4
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/abbrevs.py +29 -35
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_abbrevs.py +51 -101
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_emitter.py +18 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/LICENSE +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/README.md +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/setup.cfg +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs2confluence.egg-info/SOURCES.txt +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/cli.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/ir/document.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/config.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/nav.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/page.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/parser/markdown.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/pdf/render.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preview/render.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preview/server.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/publisher/client.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/publisher/pipeline.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/images.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_cli.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_editlink.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_extra_css.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_frontmatter.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_icons.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_images.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_internallinks.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_ir.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_linkdefs.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_loader.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_mermaid.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_page_loader.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_parser.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_pdf.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_preprocess.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_preview.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_publish_client.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_publish_config.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_publish_pipeline.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_server.py +0 -0
- {mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/tests/test_treeutil.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mkdocs2confluence"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.12"
|
|
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" }
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/emitter/xhtml.py
RENAMED
|
@@ -23,6 +23,7 @@ from urllib.parse import urlparse
|
|
|
23
23
|
|
|
24
24
|
from mkdocs_to_confluence.ir.nodes import (
|
|
25
25
|
AbbrevFootnoteNode,
|
|
26
|
+
AbbrevGlossaryBlock,
|
|
26
27
|
Admonition,
|
|
27
28
|
BlockQuote,
|
|
28
29
|
BoldNode,
|
|
@@ -191,6 +192,8 @@ def _emit_node(node: IRNode) -> str:
|
|
|
191
192
|
return _emit_front_matter(node)
|
|
192
193
|
if isinstance(node, FootnoteBlock):
|
|
193
194
|
return _emit_footnote_block(node)
|
|
195
|
+
if isinstance(node, AbbrevGlossaryBlock):
|
|
196
|
+
return _emit_abbrev_glossary_block(node)
|
|
194
197
|
if isinstance(node, GridCards):
|
|
195
198
|
return _emit_grid_cards(node)
|
|
196
199
|
if isinstance(node, UnsupportedBlock):
|
|
@@ -557,17 +560,40 @@ def _emit_footnote_ref(node: FootnoteRef) -> str:
|
|
|
557
560
|
|
|
558
561
|
|
|
559
562
|
def _emit_abbrev_footnote(node: AbbrevFootnoteNode) -> str:
|
|
560
|
-
"""
|
|
563
|
+
"""Inline: ABBR + superscript anchor-link to the glossary entry."""
|
|
564
|
+
anchor = html.escape(f"abbr-{node.number}")
|
|
565
|
+
num = html.escape(str(node.number))
|
|
561
566
|
term = html.escape(node.abbr)
|
|
562
|
-
defn = html.escape(node.definition)
|
|
563
567
|
return (
|
|
564
568
|
f"{term}"
|
|
565
|
-
f
|
|
566
|
-
f
|
|
567
|
-
f"
|
|
569
|
+
f"<sup>"
|
|
570
|
+
f'<ac:link ac:anchor="{anchor}">'
|
|
571
|
+
f"<ac:plain-text-link-body><![CDATA[{num}]]></ac:plain-text-link-body>"
|
|
572
|
+
f"</ac:link>"
|
|
573
|
+
f"</sup>"
|
|
568
574
|
)
|
|
569
575
|
|
|
570
576
|
|
|
577
|
+
def _emit_abbrev_glossary_block(node: AbbrevGlossaryBlock) -> str:
|
|
578
|
+
"""End-of-page abbreviations list with Confluence anchor targets."""
|
|
579
|
+
parts: list[str] = ["<hr />\n<h6>Abbreviations</h6>\n<ol>\n"]
|
|
580
|
+
for fn in node.footnoted:
|
|
581
|
+
anchor = html.escape(f"abbr-{fn.number}")
|
|
582
|
+
anchor_macro = (
|
|
583
|
+
f'<ac:structured-macro ac:name="anchor">'
|
|
584
|
+
f'<ac:parameter ac:name=""><![CDATA[{anchor}]]></ac:parameter>'
|
|
585
|
+
f"</ac:structured-macro>"
|
|
586
|
+
)
|
|
587
|
+
abbr = html.escape(fn.abbr)
|
|
588
|
+
defn = html.escape(fn.definition)
|
|
589
|
+
parts.append(f"<li>{anchor_macro}<strong>{abbr}</strong> — {defn}</li>\n")
|
|
590
|
+
for abbr, defn in node.extras:
|
|
591
|
+
# No anchor — these only appeared in headings/titles, no inline superscript links here.
|
|
592
|
+
parts.append(f"<li><strong>{html.escape(abbr)}</strong> — {html.escape(defn)}</li>\n")
|
|
593
|
+
parts.append("</ol>\n")
|
|
594
|
+
return "".join(parts)
|
|
595
|
+
|
|
596
|
+
|
|
571
597
|
def _emit_footnote_block(node: FootnoteBlock) -> str:
|
|
572
598
|
"""Footnotes section: heading + ordered list with anchor targets."""
|
|
573
599
|
items: list[str] = []
|
|
@@ -433,15 +433,33 @@ class FrontMatter(IRNode):
|
|
|
433
433
|
|
|
434
434
|
@dataclass(frozen=True)
|
|
435
435
|
class AbbrevFootnoteNode(IRNode):
|
|
436
|
-
"""
|
|
436
|
+
"""Inline: abbreviated term with a superscript anchor-link to the glossary.
|
|
437
437
|
|
|
438
|
-
The emitter
|
|
439
|
-
|
|
440
|
-
all footnote macros and renders their bodies at the bottom of the page.
|
|
438
|
+
The emitter renders ``ABBR<sup>[N]</sup>`` where ``[N]`` links to the
|
|
439
|
+
corresponding entry in the end-of-page :class:`AbbrevGlossaryBlock`.
|
|
441
440
|
"""
|
|
442
441
|
|
|
443
442
|
abbr: str
|
|
444
443
|
definition: str
|
|
444
|
+
number: int # 1-based, assigned by the transform in order of first encounter
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@dataclass(frozen=True)
|
|
448
|
+
class AbbrevGlossaryBlock(IRNode):
|
|
449
|
+
"""End-of-page abbreviations reference block.
|
|
450
|
+
|
|
451
|
+
Rendered as a numbered list (with Confluence anchor targets for the
|
|
452
|
+
back-links) followed by an optional bullet list of abbreviations that
|
|
453
|
+
only appeared in headings or other non-expandable contexts.
|
|
454
|
+
|
|
455
|
+
Attributes:
|
|
456
|
+
footnoted: Abbreviations annotated inline, ordered by first encounter.
|
|
457
|
+
extras: ``(abbr, definition)`` pairs for abbreviations that could
|
|
458
|
+
not be annotated inline, sorted alphabetically.
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
footnoted: tuple[AbbrevFootnoteNode, ...]
|
|
462
|
+
extras: tuple[tuple[str, str], ...]
|
|
445
463
|
|
|
446
464
|
|
|
447
465
|
# ── Footnotes ────────────────────────────────────────────────────────────────
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/abbrevs.py
RENAMED
|
@@ -30,13 +30,13 @@ from dataclasses import replace
|
|
|
30
30
|
|
|
31
31
|
from mkdocs_to_confluence.ir.nodes import (
|
|
32
32
|
AbbrevFootnoteNode,
|
|
33
|
+
AbbrevGlossaryBlock,
|
|
33
34
|
Admonition,
|
|
34
35
|
BlockQuote,
|
|
35
36
|
BoldNode,
|
|
36
37
|
BulletList,
|
|
37
38
|
ContentTabs,
|
|
38
39
|
Expandable,
|
|
39
|
-
HorizontalRule,
|
|
40
40
|
IRNode,
|
|
41
41
|
ItalicNode,
|
|
42
42
|
LinkNode,
|
|
@@ -60,13 +60,18 @@ class _State:
|
|
|
60
60
|
|
|
61
61
|
def __init__(self, abbrevs: dict[str, str]) -> None:
|
|
62
62
|
self.abbrevs = abbrevs
|
|
63
|
-
self.
|
|
63
|
+
self._expanded_list: list[str] = [] # ordered by first encounter
|
|
64
|
+
self._expanded_set: set[str] = set() # fast membership test
|
|
64
65
|
# Pre-compile word-boundary patterns once.
|
|
65
66
|
self._patterns: dict[str, re.Pattern[str]] = {
|
|
66
67
|
abbr: re.compile(r"\b" + re.escape(abbr) + r"\b")
|
|
67
68
|
for abbr in abbrevs
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
@property
|
|
72
|
+
def expanded(self) -> set[str]:
|
|
73
|
+
return self._expanded_set
|
|
74
|
+
|
|
70
75
|
def expand_to_nodes(self, text: str) -> tuple[IRNode, ...]:
|
|
71
76
|
"""Split *text* around the first unexpanded abbreviation.
|
|
72
77
|
|
|
@@ -75,7 +80,7 @@ class _State:
|
|
|
75
80
|
"""
|
|
76
81
|
best: tuple[int, int, str] | None = None
|
|
77
82
|
for abbr in self.abbrevs:
|
|
78
|
-
if abbr in self.
|
|
83
|
+
if abbr in self._expanded_set:
|
|
79
84
|
continue
|
|
80
85
|
m = self._patterns[abbr].search(text)
|
|
81
86
|
if m and (best is None or m.start() < best[0]):
|
|
@@ -85,11 +90,13 @@ class _State:
|
|
|
85
90
|
return (TextNode(text),) if text else ()
|
|
86
91
|
|
|
87
92
|
start, end, abbr = best
|
|
88
|
-
self.
|
|
93
|
+
self._expanded_list.append(abbr)
|
|
94
|
+
self._expanded_set.add(abbr)
|
|
95
|
+
number = len(self._expanded_list) # 1-based
|
|
89
96
|
nodes: list[IRNode] = []
|
|
90
97
|
if text[:start]:
|
|
91
98
|
nodes.append(TextNode(text[:start]))
|
|
92
|
-
nodes.append(AbbrevFootnoteNode(abbr=abbr, definition=self.abbrevs[abbr]))
|
|
99
|
+
nodes.append(AbbrevFootnoteNode(abbr=abbr, definition=self.abbrevs[abbr], number=number))
|
|
93
100
|
nodes.extend(self.expand_to_nodes(text[end:]))
|
|
94
101
|
return tuple(nodes)
|
|
95
102
|
|
|
@@ -197,21 +204,6 @@ def _find_mentioned(text: str, abbrevs: dict[str, str]) -> set[str]:
|
|
|
197
204
|
}
|
|
198
205
|
|
|
199
206
|
|
|
200
|
-
def _build_glossary_section(terms: dict[str, str]) -> tuple[IRNode, ...]:
|
|
201
|
-
"""Return an HR + h6 ``Section`` listing abbreviations that could not be footnoted."""
|
|
202
|
-
items = tuple(
|
|
203
|
-
ListItem(children=(Paragraph(children=(TextNode(f"{abbr} — {defn}"),)),))
|
|
204
|
-
for abbr, defn in sorted(terms.items())
|
|
205
|
-
)
|
|
206
|
-
section = Section(
|
|
207
|
-
level=6,
|
|
208
|
-
anchor="glossary",
|
|
209
|
-
title=(TextNode("Glossary"),),
|
|
210
|
-
children=(BulletList(items=items),),
|
|
211
|
-
)
|
|
212
|
-
return (HorizontalRule(), section)
|
|
213
|
-
|
|
214
|
-
|
|
215
207
|
# ── Public API ────────────────────────────────────────────────────────────────
|
|
216
208
|
|
|
217
209
|
|
|
@@ -229,15 +221,15 @@ def apply_abbreviations(
|
|
|
229
221
|
:func:`~mkdocs_to_confluence.preprocess.abbrevs.extract_abbreviations`.
|
|
230
222
|
page_text: The preprocessed page text (after stripping abbreviation
|
|
231
223
|
definition lines) used to detect which abbreviations are
|
|
232
|
-
actually present on the page.
|
|
233
|
-
abbreviations need a glossary entry.
|
|
224
|
+
actually present on the page.
|
|
234
225
|
|
|
235
226
|
Returns:
|
|
236
|
-
Modified node tuple. Abbreviations in body text
|
|
237
|
-
:class
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
227
|
+
Modified node tuple. Abbreviations in body text receive an inline
|
|
228
|
+
superscript anchor-link (:class:`AbbrevFootnoteNode`). An
|
|
229
|
+
:class:`AbbrevGlossaryBlock` is appended when any abbreviation was
|
|
230
|
+
mentioned on the page, listing all footnoted entries (with anchor
|
|
231
|
+
targets for the back-links) plus any abbreviations that only appeared
|
|
232
|
+
in headings or other non-expandable contexts.
|
|
241
233
|
"""
|
|
242
234
|
if not abbrevs:
|
|
243
235
|
return nodes
|
|
@@ -246,15 +238,17 @@ def apply_abbreviations(
|
|
|
246
238
|
transformed = tuple(_transform_block(n, state) for n in nodes)
|
|
247
239
|
|
|
248
240
|
mentioned = _find_mentioned(page_text, abbrevs)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
abbr
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
241
|
+
footnoted = tuple(
|
|
242
|
+
AbbrevFootnoteNode(abbr=abbr, definition=abbrevs[abbr], number=i + 1)
|
|
243
|
+
for i, abbr in enumerate(state._expanded_list)
|
|
244
|
+
)
|
|
245
|
+
extras = tuple(
|
|
246
|
+
(abbr, abbrevs[abbr])
|
|
247
|
+
for abbr in sorted(mentioned - state._expanded_set)
|
|
248
|
+
)
|
|
255
249
|
|
|
256
|
-
if
|
|
257
|
-
transformed = transformed +
|
|
250
|
+
if footnoted or extras:
|
|
251
|
+
transformed = transformed + (AbbrevGlossaryBlock(footnoted=footnoted, extras=extras),)
|
|
258
252
|
|
|
259
253
|
return transformed
|
|
260
254
|
|
|
@@ -4,11 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from mkdocs_to_confluence.ir.nodes import (
|
|
6
6
|
AbbrevFootnoteNode,
|
|
7
|
+
AbbrevGlossaryBlock,
|
|
7
8
|
Admonition,
|
|
8
9
|
BoldNode,
|
|
9
|
-
BulletList,
|
|
10
10
|
CodeBlock,
|
|
11
|
-
HorizontalRule,
|
|
12
11
|
LinkNode,
|
|
13
12
|
Paragraph,
|
|
14
13
|
Section,
|
|
@@ -89,17 +88,22 @@ def test_expands_first_occurrence_as_footnote():
|
|
|
89
88
|
para = result[0]
|
|
90
89
|
assert isinstance(para, Paragraph)
|
|
91
90
|
children = para.children
|
|
92
|
-
#
|
|
91
|
+
# TextNode("The ") + AbbrevFootnoteNode + TextNode(" platform handles IAM requests.")
|
|
93
92
|
assert len(children) == 3
|
|
94
|
-
assert isinstance(children[0], TextNode)
|
|
95
|
-
|
|
96
|
-
assert isinstance(
|
|
97
|
-
assert
|
|
98
|
-
assert
|
|
93
|
+
assert isinstance(children[0], TextNode) and children[0].text == "The "
|
|
94
|
+
fn = children[1]
|
|
95
|
+
assert isinstance(fn, AbbrevFootnoteNode)
|
|
96
|
+
assert fn.abbr == "IAM"
|
|
97
|
+
assert fn.definition == "Identity and Access Management"
|
|
98
|
+
assert fn.number == 1
|
|
99
99
|
# Second occurrence left as plain text
|
|
100
100
|
assert isinstance(children[2], TextNode)
|
|
101
101
|
assert "IAM requests." in children[2].text
|
|
102
|
-
|
|
102
|
+
# Glossary block appended
|
|
103
|
+
glossary = result[1]
|
|
104
|
+
assert isinstance(glossary, AbbrevGlossaryBlock)
|
|
105
|
+
assert glossary.footnoted[0].abbr == "IAM"
|
|
106
|
+
assert glossary.footnoted[0].number == 1
|
|
103
107
|
|
|
104
108
|
|
|
105
109
|
def test_expands_multiple_different_abbrevs():
|
|
@@ -107,11 +111,12 @@ def test_expands_multiple_different_abbrevs():
|
|
|
107
111
|
nodes = (_para("Use IAM and RBAC for access control."),)
|
|
108
112
|
result = apply_abbreviations(nodes, abbrevs, page_text="Use IAM and RBAC for access control.")
|
|
109
113
|
para = result[0]
|
|
110
|
-
assert isinstance(para, Paragraph)
|
|
111
114
|
footnotes = [c for c in para.children if isinstance(c, AbbrevFootnoteNode)]
|
|
112
115
|
abbrs = {fn.abbr for fn in footnotes}
|
|
113
116
|
assert "IAM" in abbrs
|
|
114
117
|
assert "RBAC" in abbrs
|
|
118
|
+
numbers = sorted(fn.number for fn in footnotes)
|
|
119
|
+
assert numbers == [1, 2]
|
|
115
120
|
|
|
116
121
|
|
|
117
122
|
def test_no_expand_when_no_abbrevs():
|
|
@@ -122,65 +127,40 @@ def test_no_expand_when_no_abbrevs():
|
|
|
122
127
|
|
|
123
128
|
def test_no_expand_in_section_heading():
|
|
124
129
|
abbrevs = {"IAM": "Identity and Access Management"}
|
|
125
|
-
section = Section(
|
|
126
|
-
level=2,
|
|
127
|
-
anchor="iam",
|
|
128
|
-
title=(TextNode("IAM Platform"),),
|
|
129
|
-
children=(),
|
|
130
|
-
)
|
|
130
|
+
section = Section(level=2, anchor="iam", title=(TextNode("IAM Platform"),), children=())
|
|
131
131
|
result = apply_abbreviations((section,), abbrevs, page_text="IAM Platform")
|
|
132
132
|
heading_text = result[0].title[0].text # type: ignore[union-attr]
|
|
133
|
-
assert heading_text == "IAM Platform" # not
|
|
133
|
+
assert heading_text == "IAM Platform" # not annotated
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
def
|
|
136
|
+
def test_glossary_block_appended_for_heading_only_abbrev():
|
|
137
137
|
abbrevs = {"IAM": "Identity and Access Management"}
|
|
138
|
-
section = Section(
|
|
139
|
-
level=2,
|
|
140
|
-
anchor="iam",
|
|
141
|
-
title=(TextNode("IAM Platform"),),
|
|
142
|
-
children=(),
|
|
143
|
-
)
|
|
138
|
+
section = Section(level=2, anchor="iam", title=(TextNode("IAM Platform"),), children=())
|
|
144
139
|
result = apply_abbreviations((section,), abbrevs, page_text="IAM Platform")
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
assert isinstance(
|
|
148
|
-
glossary
|
|
149
|
-
assert
|
|
150
|
-
assert glossary.anchor == "glossary"
|
|
151
|
-
assert glossary.level == 6
|
|
152
|
-
bullet_list = glossary.children[0]
|
|
153
|
-
assert isinstance(bullet_list, BulletList)
|
|
154
|
-
item_text = bullet_list.items[0].children[0].children[0].text # type: ignore[union-attr]
|
|
155
|
-
assert "IAM" in item_text
|
|
156
|
-
assert "Identity and Access Management" in item_text
|
|
140
|
+
assert len(result) == 2
|
|
141
|
+
glossary = result[1]
|
|
142
|
+
assert isinstance(glossary, AbbrevGlossaryBlock)
|
|
143
|
+
assert len(glossary.footnoted) == 0
|
|
144
|
+
assert glossary.extras == (("IAM", "Identity and Access Management"),)
|
|
157
145
|
|
|
158
146
|
|
|
159
147
|
def test_no_glossary_when_abbrev_footnoted_inline():
|
|
160
148
|
abbrevs = {"IAM": "Identity and Access Management"}
|
|
161
149
|
nodes = (_para("The IAM platform."),)
|
|
162
150
|
result = apply_abbreviations(nodes, abbrevs, page_text="The IAM platform.")
|
|
163
|
-
|
|
164
|
-
assert
|
|
165
|
-
assert
|
|
151
|
+
assert len(result) == 2
|
|
152
|
+
assert isinstance(result[1], AbbrevGlossaryBlock)
|
|
153
|
+
assert len(result[1].extras) == 0
|
|
166
154
|
|
|
167
155
|
|
|
168
156
|
def test_no_expand_in_table_header_cell():
|
|
169
157
|
abbrevs = {"API": "Application Programming Interface"}
|
|
170
|
-
header = TableRow(cells=(
|
|
171
|
-
|
|
172
|
-
))
|
|
173
|
-
body = TableRow(cells=(
|
|
174
|
-
TableCell(children=(TextNode("The API docs"),), is_header=False),
|
|
175
|
-
))
|
|
158
|
+
header = TableRow(cells=(TableCell(children=(TextNode("API Endpoint"),), is_header=True),))
|
|
159
|
+
body = TableRow(cells=(TableCell(children=(TextNode("The API docs"),), is_header=False),))
|
|
176
160
|
table = Table(header=header, rows=(body,))
|
|
177
161
|
result = apply_abbreviations((table,), abbrevs, page_text="API Endpoint The API docs")
|
|
178
|
-
|
|
179
|
-
# Header cell: not expanded
|
|
180
162
|
header_text = result[0].header.cells[0].children[0].text # type: ignore[union-attr]
|
|
181
163
|
assert header_text == "API Endpoint"
|
|
182
|
-
|
|
183
|
-
# Body cell: first occurrence footnoted
|
|
184
164
|
body_children = result[0].rows[0].cells[0].children # type: ignore[union-attr]
|
|
185
165
|
assert any(isinstance(c, AbbrevFootnoteNode) and c.abbr == "API" for c in body_children)
|
|
186
166
|
|
|
@@ -188,14 +168,11 @@ def test_no_expand_in_table_header_cell():
|
|
|
188
168
|
def test_no_expand_in_admonition_title():
|
|
189
169
|
abbrevs = {"TLS": "Transport Layer Security"}
|
|
190
170
|
admonition = Admonition(
|
|
191
|
-
kind="note",
|
|
192
|
-
title="TLS Configuration",
|
|
171
|
+
kind="note", title="TLS Configuration",
|
|
193
172
|
children=(_para("Use TLS for encryption."),),
|
|
194
173
|
)
|
|
195
174
|
result = apply_abbreviations((admonition,), abbrevs, page_text="TLS Configuration Use TLS for encryption.")
|
|
196
|
-
# Title is str, unchanged
|
|
197
175
|
assert result[0].title == "TLS Configuration" # type: ignore[union-attr]
|
|
198
|
-
# Body paragraph: TLS footnoted
|
|
199
176
|
body_children = result[0].children[0].children # type: ignore[union-attr]
|
|
200
177
|
assert any(isinstance(c, AbbrevFootnoteNode) and c.abbr == "TLS" for c in body_children)
|
|
201
178
|
|
|
@@ -203,28 +180,21 @@ def test_no_expand_in_admonition_title():
|
|
|
203
180
|
def test_no_expand_in_code_block():
|
|
204
181
|
abbrevs = {"SQL": "Structured Query Language"}
|
|
205
182
|
code = CodeBlock(code="SELECT * FROM SQL_table", language="sql")
|
|
206
|
-
|
|
207
|
-
result
|
|
208
|
-
assert result[0].code == "SELECT * FROM SQL_table"
|
|
183
|
+
result = apply_abbreviations((code,), abbrevs, page_text="SELECT * FROM SQL_table")
|
|
184
|
+
assert result[0].code == "SELECT * FROM SQL_table" # type: ignore[union-attr]
|
|
209
185
|
|
|
210
186
|
|
|
211
187
|
def test_no_expand_in_link_text():
|
|
212
188
|
abbrevs = {"CLI": "Command Line Interface"}
|
|
213
|
-
link = LinkNode(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
)
|
|
217
|
-
para = Paragraph(children=(link,))
|
|
218
|
-
result = apply_abbreviations((para,), abbrevs, page_text="CLI tools")
|
|
219
|
-
link_text = result[0].children[0].children[0].text # type: ignore[union-attr]
|
|
220
|
-
assert link_text == "CLI tools" # not expanded inside link
|
|
189
|
+
link = LinkNode(href="https://example.com", children=(TextNode("CLI tools"),))
|
|
190
|
+
result = apply_abbreviations((Paragraph(children=(link,)),), abbrevs, page_text="CLI tools")
|
|
191
|
+
assert result[0].children[0].children[0].text == "CLI tools" # type: ignore[union-attr]
|
|
221
192
|
|
|
222
193
|
|
|
223
194
|
def test_expands_inside_bold():
|
|
224
195
|
abbrevs = {"CI": "Continuous Integration"}
|
|
225
196
|
bold = BoldNode(children=(TextNode("CI pipeline"),))
|
|
226
|
-
|
|
227
|
-
result = apply_abbreviations((para,), abbrevs, page_text="CI pipeline")
|
|
197
|
+
result = apply_abbreviations((Paragraph(children=(bold,)),), abbrevs, page_text="CI pipeline")
|
|
228
198
|
bold_children = result[0].children[0].children # type: ignore[union-attr]
|
|
229
199
|
assert any(isinstance(c, AbbrevFootnoteNode) and c.abbr == "CI" for c in bold_children)
|
|
230
200
|
|
|
@@ -234,53 +204,33 @@ def test_word_boundary_not_partial_match():
|
|
|
234
204
|
nodes = (_para("The RAPID response via API."),)
|
|
235
205
|
result = apply_abbreviations(nodes, abbrevs, page_text="The RAPID response via API.")
|
|
236
206
|
para = result[0]
|
|
237
|
-
assert isinstance(para, Paragraph)
|
|
238
|
-
# RAPID should be untouched; API should be footnoted
|
|
239
207
|
all_text = "".join(c.text for c in para.children if isinstance(c, TextNode))
|
|
240
208
|
assert "RAPID" in all_text
|
|
241
|
-
|
|
242
|
-
assert any(fn.abbr == "API" for fn in footnotes)
|
|
209
|
+
assert any(isinstance(c, AbbrevFootnoteNode) and c.abbr == "API" for c in para.children)
|
|
243
210
|
|
|
244
211
|
|
|
245
|
-
def
|
|
246
|
-
# Abbreviations expanded inline via footnote should NOT also appear in a glossary.
|
|
212
|
+
def test_footnoted_abbrevs_not_in_extras():
|
|
247
213
|
abbrevs = {"API": "Application Programming Interface", "IAM": "Identity and Access Management"}
|
|
248
214
|
nodes = (_para("Use the API and IAM to authenticate."),)
|
|
249
|
-
result = apply_abbreviations(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
assert len(
|
|
255
|
-
para = result[0]
|
|
256
|
-
assert isinstance(para, Paragraph)
|
|
257
|
-
footnotes = [c for c in para.children if isinstance(c, AbbrevFootnoteNode)]
|
|
258
|
-
assert {fn.abbr for fn in footnotes} == {"API", "IAM"}
|
|
215
|
+
result = apply_abbreviations(nodes, abbrevs, page_text="Use the API and IAM to authenticate.")
|
|
216
|
+
assert len(result) == 2
|
|
217
|
+
glossary = result[1]
|
|
218
|
+
assert isinstance(glossary, AbbrevGlossaryBlock)
|
|
219
|
+
assert {fn.abbr for fn in glossary.footnoted} == {"API", "IAM"}
|
|
220
|
+
assert len(glossary.extras) == 0
|
|
259
221
|
|
|
260
222
|
|
|
261
223
|
def test_abbrev_not_in_text_produces_no_glossary():
|
|
262
224
|
abbrevs = {"XYZ": "Some Definition"}
|
|
263
|
-
|
|
264
|
-
result = apply_abbreviations(nodes, abbrevs, page_text="Nothing relevant here.")
|
|
265
|
-
# XYZ never mentioned → no glossary
|
|
225
|
+
result = apply_abbreviations((_para("Nothing relevant here."),), abbrevs, page_text="Nothing relevant here.")
|
|
266
226
|
assert len(result) == 1
|
|
267
227
|
|
|
268
228
|
|
|
269
|
-
def
|
|
229
|
+
def test_extras_sorted_alphabetically():
|
|
270
230
|
abbrevs = {"RBAC": "Role-Based Access Control", "IAM": "Identity and Access Management"}
|
|
271
|
-
|
|
272
|
-
section =
|
|
273
|
-
level=1,
|
|
274
|
-
anchor="overview",
|
|
275
|
-
title=(TextNode("IAM and RBAC Overview"),),
|
|
276
|
-
children=(),
|
|
277
|
-
)
|
|
278
|
-
result = apply_abbreviations(
|
|
279
|
-
(section,), abbrevs, page_text="IAM and RBAC Overview"
|
|
280
|
-
)
|
|
231
|
+
section = Section(level=1, anchor="overview", title=(TextNode("IAM and RBAC Overview"),), children=())
|
|
232
|
+
result = apply_abbreviations((section,), abbrevs, page_text="IAM and RBAC Overview")
|
|
281
233
|
glossary = result[-1]
|
|
282
|
-
assert isinstance(glossary,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
assert labels == sorted(labels)
|
|
286
|
-
|
|
234
|
+
assert isinstance(glossary, AbbrevGlossaryBlock)
|
|
235
|
+
extra_abbrs = [abbr for abbr, _ in glossary.extras]
|
|
236
|
+
assert extra_abbrs == sorted(extra_abbrs)
|
|
@@ -402,6 +402,24 @@ class TestFootnoteEmitter:
|
|
|
402
402
|
assert 'fn-1' in html_out
|
|
403
403
|
assert 'My note.' in html_out
|
|
404
404
|
|
|
405
|
+
def test_abbrev_glossary_extras_in_ol_not_ul(self) -> None:
|
|
406
|
+
"""Abbreviations only in headings/titles must appear in <ol>, not <ul>."""
|
|
407
|
+
from mkdocs_to_confluence.emitter.xhtml import emit
|
|
408
|
+
from mkdocs_to_confluence.ir.nodes import AbbrevFootnoteNode, AbbrevGlossaryBlock
|
|
409
|
+
fn = AbbrevFootnoteNode(abbr="API", definition="Application Programming Interface", number=1)
|
|
410
|
+
block = AbbrevGlossaryBlock(
|
|
411
|
+
footnoted=(fn,),
|
|
412
|
+
extras=(("AD", "Active Directory"),),
|
|
413
|
+
)
|
|
414
|
+
out = emit((block,))
|
|
415
|
+
assert "<ol>" in out
|
|
416
|
+
assert "<ul>" not in out
|
|
417
|
+
assert "AD" in out
|
|
418
|
+
assert "Active Directory" in out
|
|
419
|
+
assert "API" in out
|
|
420
|
+
# extras must NOT get an anchor macro
|
|
421
|
+
assert out.count('ac:name="anchor"') == 1 # only for the footnoted entry
|
|
422
|
+
|
|
405
423
|
|
|
406
424
|
# ── Inline HTML emitters ──────────────────────────────────────────────────────
|
|
407
425
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs2confluence.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs2confluence.egg-info/requires.txt
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs2confluence.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/emitter/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/ir/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/ir/document.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/ir/treeutil.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/config.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/extra_css.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/nav.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/loader/page.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/parser/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/parser/markdown.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/pdf/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/pdf/generator.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/pdf/render.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/abbrevs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/fence.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preprocess/icons.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preview/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preview/render.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/preview/server.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/publisher/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/publisher/client.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/publisher/pipeline.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/assets.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/images.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.7.10 → mkdocs2confluence-0.7.12}/src/mkdocs_to_confluence/transforms/mermaid.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|