mkdocs2confluence 0.10.0__tar.gz → 0.10.2__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.10.0 → mkdocs2confluence-0.10.2}/PKG-INFO +3 -1
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/README.md +2 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/pyproject.toml +1 -1
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/PKG-INFO +3 -1
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/cli.py +11 -6
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/emitter/xhtml.py +13 -9
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/parser/markdown.py +62 -8
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/publisher/pipeline.py +6 -1
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_emitter.py +26 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_internallinks.py +58 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_parser.py +43 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/LICENSE +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/setup.cfg +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/SOURCES.txt +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/ir/document.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/ir/nodes.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/config.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/nav.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/page.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/pdf/render.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preview/render.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preview/server.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/publisher/client.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/anchoring.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/command.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/comments.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/github.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/platform.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/state.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/footer.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/images.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_abbrevs.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_children_macro.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_cli.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_editlink.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_extra_css.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_footer.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_frontmatter.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_icons.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_images.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_ir.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_linkdefs.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_loader.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_mermaid.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_page_loader.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_pdf.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_preprocess.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_preview.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_publish_client.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_publish_config.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_publish_pipeline.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_server.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_sync_anchoring.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_sync_command.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_sync_comments.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_sync_github.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_sync_state.py +0 -0
- {mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/tests/test_treeutil.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.2
|
|
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
|
|
@@ -52,6 +52,8 @@ Dynamic: license-file
|
|
|
52
52
|
[](https://github.com/astral-sh/ruff)
|
|
53
53
|
[](https://mypy-lang.org/)
|
|
54
54
|
[](https://github.com/PyCQA/bandit)
|
|
55
|
+
[](https://slsa.dev)
|
|
56
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/jeckyl2010/mkdocs2confluence)
|
|
55
57
|
|
|
56
58
|
A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence storage XHTML and publishes it directly to Confluence Cloud. It is a **compiler/transpiler**, not an HTML converter — every construct maps to its native Confluence equivalent, so pages look and behave like hand-authored Confluence content.
|
|
57
59
|
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
[](https://github.com/astral-sh/ruff)
|
|
12
12
|
[](https://mypy-lang.org/)
|
|
13
13
|
[](https://github.com/PyCQA/bandit)
|
|
14
|
+
[](https://slsa.dev)
|
|
15
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/jeckyl2010/mkdocs2confluence)
|
|
14
16
|
|
|
15
17
|
A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence storage XHTML and publishes it directly to Confluence Cloud. It is a **compiler/transpiler**, not an HTML converter — every construct maps to its native Confluence equivalent, so pages look and behave like hand-authored Confluence content.
|
|
16
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mkdocs2confluence"
|
|
3
|
-
version = "0.10.
|
|
3
|
+
version = "0.10.2"
|
|
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.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs2confluence
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.2
|
|
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
|
|
@@ -52,6 +52,8 @@ Dynamic: license-file
|
|
|
52
52
|
[](https://github.com/astral-sh/ruff)
|
|
53
53
|
[](https://mypy-lang.org/)
|
|
54
54
|
[](https://github.com/PyCQA/bandit)
|
|
55
|
+
[](https://slsa.dev)
|
|
56
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/jeckyl2010/mkdocs2confluence)
|
|
55
57
|
|
|
56
58
|
A Python CLI tool that compiles MkDocs-flavoured Markdown into native Confluence storage XHTML and publishes it directly to Confluence Cloud. It is a **compiler/transpiler**, not an HTML converter — every construct maps to its native Confluence equivalent, so pages look and behave like hand-authored Confluence content.
|
|
57
59
|
|
|
@@ -312,7 +312,7 @@ def _cmd_preview(args: argparse.Namespace) -> None:
|
|
|
312
312
|
config = load_config(config_path)
|
|
313
313
|
configure_styles(config.extra_styles)
|
|
314
314
|
|
|
315
|
-
|
|
315
|
+
all_nodes = resolve_nav(config)
|
|
316
316
|
|
|
317
317
|
section_given = bool(getattr(args, "section", None))
|
|
318
318
|
page_given = bool(args.page)
|
|
@@ -326,8 +326,9 @@ def _cmd_preview(args: argparse.Namespace) -> None:
|
|
|
326
326
|
sys.exit(1)
|
|
327
327
|
|
|
328
328
|
# Resolve section subtree (both single-page and section-mode use this)
|
|
329
|
+
nodes = all_nodes
|
|
329
330
|
if section_given:
|
|
330
|
-
section_node = find_section(
|
|
331
|
+
section_node = find_section(all_nodes, args.section) or find_section_by_folder(all_nodes, args.section)
|
|
331
332
|
if section_node is None:
|
|
332
333
|
print(f"error: section '{args.section}' not found in nav.", file=sys.stderr)
|
|
333
334
|
sys.exit(1)
|
|
@@ -347,7 +348,7 @@ def _cmd_preview(args: argparse.Namespace) -> None:
|
|
|
347
348
|
out_dir, index_name = _parse_out_path(args.out)
|
|
348
349
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
349
350
|
|
|
350
|
-
link_map = build_link_map(
|
|
351
|
+
link_map = build_link_map(all_nodes)
|
|
351
352
|
page_link_map = {
|
|
352
353
|
node.title: f"{Path(node.docs_path).stem}.html"
|
|
353
354
|
for node in pages
|
|
@@ -479,11 +480,12 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
479
480
|
)
|
|
480
481
|
sys.exit(1)
|
|
481
482
|
|
|
482
|
-
|
|
483
|
+
all_nav_nodes = resolve_nav(config)
|
|
484
|
+
nav_nodes = all_nav_nodes
|
|
483
485
|
|
|
484
486
|
# Section filter (--section takes precedence; --page is a secondary filter)
|
|
485
487
|
if getattr(args, "section", None):
|
|
486
|
-
section_node = find_section(
|
|
488
|
+
section_node = find_section(all_nav_nodes, args.section) or find_section_by_folder(all_nav_nodes, args.section)
|
|
487
489
|
if section_node is None:
|
|
488
490
|
print(f"error: section '{args.section}' not found in nav.", file=sys.stderr)
|
|
489
491
|
sys.exit(1)
|
|
@@ -529,7 +531,10 @@ def _cmd_publish(args: argparse.Namespace) -> None:
|
|
|
529
531
|
file=sys.stderr,
|
|
530
532
|
)
|
|
531
533
|
sys.exit(1)
|
|
532
|
-
plan = plan_publish(
|
|
534
|
+
plan = plan_publish(
|
|
535
|
+
nav_nodes, client, config, conf_config,
|
|
536
|
+
space_id=space_id, quiet=args.quiet, full_nav_nodes=all_nav_nodes,
|
|
537
|
+
)
|
|
533
538
|
# --prune is silently disabled for partial publishes (--page / --section)
|
|
534
539
|
# because published_ids would only cover the subset, not the full nav.
|
|
535
540
|
partial = bool(getattr(args, "page", None) or getattr(args, "section", None))
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/emitter/xhtml.py
RENAMED
|
@@ -422,15 +422,19 @@ def _emit_task_item(item: ListItem) -> str:
|
|
|
422
422
|
|
|
423
423
|
|
|
424
424
|
def _emit_list_item(item: ListItem) -> str:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
425
|
+
first_block = next(
|
|
426
|
+
(i for i, c in enumerate(item.children) if isinstance(c, _LIST_BLOCK_TYPES)), None
|
|
427
|
+
)
|
|
428
|
+
if first_block is None:
|
|
429
|
+
# All inline — standard tight rendering.
|
|
430
|
+
inner = _emit_inlines(item.children)
|
|
431
|
+
return f" <li><p>{inner}</p></li>\n"
|
|
432
|
+
# Mixed: inline prefix (if any) wrapped in <p>, followed by block children.
|
|
433
|
+
parts: list[str] = []
|
|
434
|
+
if first_block > 0:
|
|
435
|
+
parts.append(f"<p>{_emit_inlines(item.children[:first_block])}</p>\n")
|
|
436
|
+
parts.append(emit(item.children[first_block:]))
|
|
437
|
+
return f" <li>{''.join(parts).strip()}</li>\n"
|
|
434
438
|
|
|
435
439
|
|
|
436
440
|
def _emit_table(node: Table) -> str:
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/parser/markdown.py
RENAMED
|
@@ -158,6 +158,7 @@ class _BlockQuoteToken:
|
|
|
158
158
|
class _ListItemData:
|
|
159
159
|
text: str
|
|
160
160
|
task: bool | None = None # None=regular, True=checked, False=unchecked
|
|
161
|
+
sub_tokens: list[_Token] = field(default_factory=list)
|
|
161
162
|
|
|
162
163
|
|
|
163
164
|
@dataclass
|
|
@@ -501,7 +502,8 @@ def _tokenize(text: str) -> list[_Token]:
|
|
|
501
502
|
task = task_m.group("state").lower() == "x"
|
|
502
503
|
item_text = task_m.group("rest")
|
|
503
504
|
i += 1
|
|
504
|
-
# Collect continuation
|
|
505
|
+
# Collect continuation and indented sub-list lines.
|
|
506
|
+
sub_lines: list[str] = []
|
|
505
507
|
while i < len(lines) and lines[i].strip():
|
|
506
508
|
cont = lines[i]
|
|
507
509
|
bullet_m = _BULLET_RE.match(cont)
|
|
@@ -510,9 +512,18 @@ def _tokenize(text: str) -> list[_Token]:
|
|
|
510
512
|
ordered_m2 and not ordered_m2.group("indent")
|
|
511
513
|
):
|
|
512
514
|
break
|
|
513
|
-
|
|
515
|
+
if (bullet_m and bullet_m.group("indent")) or (
|
|
516
|
+
ordered_m2 and ordered_m2.group("indent")
|
|
517
|
+
) or sub_lines:
|
|
518
|
+
sub_lines.append(cont)
|
|
519
|
+
else:
|
|
520
|
+
item_text = item_text.rstrip() + " " + cont.strip()
|
|
514
521
|
i += 1
|
|
515
|
-
|
|
522
|
+
_sub: list[_Token] = []
|
|
523
|
+
if sub_lines:
|
|
524
|
+
_ind = min(len(ln) - len(ln.lstrip()) for ln in sub_lines if ln.strip())
|
|
525
|
+
_sub = _tokenize("\n".join(ln[_ind:] for ln in sub_lines))
|
|
526
|
+
list_items.append(_ListItemData(text=item_text, task=task, sub_tokens=_sub))
|
|
516
527
|
tokens.append(_BulletListToken(items=list_items))
|
|
517
528
|
continue
|
|
518
529
|
|
|
@@ -537,7 +548,8 @@ def _tokenize(text: str) -> list[_Token]:
|
|
|
537
548
|
break
|
|
538
549
|
item_text = om.group("text")
|
|
539
550
|
i += 1
|
|
540
|
-
# Collect continuation
|
|
551
|
+
# Collect continuation and indented sub-list lines.
|
|
552
|
+
sub_lines_ord: list[str] = []
|
|
541
553
|
while i < len(lines) and lines[i].strip():
|
|
542
554
|
cont = lines[i]
|
|
543
555
|
ordered_m3 = _ORDERED_RE.match(cont)
|
|
@@ -546,9 +558,18 @@ def _tokenize(text: str) -> list[_Token]:
|
|
|
546
558
|
bullet_m2 and not bullet_m2.group("indent")
|
|
547
559
|
):
|
|
548
560
|
break
|
|
549
|
-
|
|
561
|
+
if (ordered_m3 and ordered_m3.group("indent")) or (
|
|
562
|
+
bullet_m2 and bullet_m2.group("indent")
|
|
563
|
+
) or sub_lines_ord:
|
|
564
|
+
sub_lines_ord.append(cont)
|
|
565
|
+
else:
|
|
566
|
+
item_text = item_text.rstrip() + " " + cont.strip()
|
|
550
567
|
i += 1
|
|
551
|
-
|
|
568
|
+
_sub_ord: list[_Token] = []
|
|
569
|
+
if sub_lines_ord:
|
|
570
|
+
_ind_ord = min(len(ln) - len(ln.lstrip()) for ln in sub_lines_ord if ln.strip())
|
|
571
|
+
_sub_ord = _tokenize("\n".join(ln[_ind_ord:] for ln in sub_lines_ord))
|
|
572
|
+
ord_items.append(_ListItemData(text=item_text, sub_tokens=_sub_ord))
|
|
552
573
|
tokens.append(_OrderedListToken(start=start, items=ord_items))
|
|
553
574
|
continue
|
|
554
575
|
|
|
@@ -1030,6 +1051,32 @@ class _OpenSection:
|
|
|
1030
1051
|
children: list[IRNode] = field(default_factory=list)
|
|
1031
1052
|
|
|
1032
1053
|
|
|
1054
|
+
def _build_sub_list_nodes(tokens: list[_Token], fn_map: dict[str, int] | None) -> tuple[IRNode, ...]:
|
|
1055
|
+
"""Recursively convert sub-list tokens (from indented continuation) to IR nodes."""
|
|
1056
|
+
nodes: list[IRNode] = []
|
|
1057
|
+
for token in tokens:
|
|
1058
|
+
if isinstance(token, _BulletListToken):
|
|
1059
|
+
items = tuple(
|
|
1060
|
+
ListItem(
|
|
1061
|
+
children=_parse_inline(item.text, fn_map=fn_map)
|
|
1062
|
+
+ _build_sub_list_nodes(item.sub_tokens, fn_map),
|
|
1063
|
+
task=item.task,
|
|
1064
|
+
)
|
|
1065
|
+
for item in token.items
|
|
1066
|
+
)
|
|
1067
|
+
nodes.append(BulletList(items=items))
|
|
1068
|
+
elif isinstance(token, _OrderedListToken):
|
|
1069
|
+
items = tuple(
|
|
1070
|
+
ListItem(
|
|
1071
|
+
children=_parse_inline(item.text, fn_map=fn_map)
|
|
1072
|
+
+ _build_sub_list_nodes(item.sub_tokens, fn_map),
|
|
1073
|
+
)
|
|
1074
|
+
for item in token.items
|
|
1075
|
+
)
|
|
1076
|
+
nodes.append(OrderedList(items=items, start=token.start))
|
|
1077
|
+
return tuple(nodes)
|
|
1078
|
+
|
|
1079
|
+
|
|
1033
1080
|
def _build_tree(
|
|
1034
1081
|
tokens: list[_Token],
|
|
1035
1082
|
fn_map: dict[str, int] | None = None,
|
|
@@ -1101,14 +1148,21 @@ def _build_tree(
|
|
|
1101
1148
|
|
|
1102
1149
|
elif isinstance(token, _BulletListToken):
|
|
1103
1150
|
items = tuple(
|
|
1104
|
-
ListItem(
|
|
1151
|
+
ListItem(
|
|
1152
|
+
children=_parse_inline(item.text, fn_map=_fn)
|
|
1153
|
+
+ _build_sub_list_nodes(item.sub_tokens, _fn),
|
|
1154
|
+
task=item.task,
|
|
1155
|
+
)
|
|
1105
1156
|
for item in token.items
|
|
1106
1157
|
)
|
|
1107
1158
|
_append_content(BulletList(items=items), stack, root)
|
|
1108
1159
|
|
|
1109
1160
|
elif isinstance(token, _OrderedListToken):
|
|
1110
1161
|
items = tuple(
|
|
1111
|
-
ListItem(
|
|
1162
|
+
ListItem(
|
|
1163
|
+
children=_parse_inline(item.text, fn_map=_fn)
|
|
1164
|
+
+ _build_sub_list_nodes(item.sub_tokens, _fn),
|
|
1165
|
+
)
|
|
1112
1166
|
for item in token.items
|
|
1113
1167
|
)
|
|
1114
1168
|
_append_content(OrderedList(items=items, start=token.start), stack, root)
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/publisher/pipeline.py
RENAMED
|
@@ -238,15 +238,20 @@ def plan_publish(
|
|
|
238
238
|
*,
|
|
239
239
|
space_id: str,
|
|
240
240
|
quiet: bool = False,
|
|
241
|
+
full_nav_nodes: list[NavNode] | None = None,
|
|
241
242
|
) -> list[PageAction]:
|
|
242
243
|
"""Build a publish plan for the entire nav tree.
|
|
243
244
|
|
|
244
245
|
Section nodes become native Confluence folders so the hierarchy is
|
|
245
246
|
preserved visually. The actual find-or-create for folders is deferred
|
|
246
247
|
to execute time once parent folder IDs are known.
|
|
248
|
+
|
|
249
|
+
``full_nav_nodes``, when provided, is used to build the link map so that
|
|
250
|
+
cross-section internal links resolve correctly even when publishing only a
|
|
251
|
+
subset of the nav (e.g. ``--section``).
|
|
247
252
|
"""
|
|
248
253
|
actions: list[PageAction] = []
|
|
249
|
-
link_map = build_link_map(nav_nodes)
|
|
254
|
+
link_map = build_link_map(full_nav_nodes if full_nav_nodes is not None else nav_nodes)
|
|
250
255
|
if not quiet:
|
|
251
256
|
print("Planning...")
|
|
252
257
|
_plan_nodes(nav_nodes, client, config, space_id, conf_config.parent_page_id, False, actions, link_map, quiet=quiet)
|
|
@@ -235,6 +235,32 @@ class TestListEmitters:
|
|
|
235
235
|
assert "<ul>" in out
|
|
236
236
|
assert "<ac:task-list>" not in out
|
|
237
237
|
|
|
238
|
+
def test_nested_bullet_list(self) -> None:
|
|
239
|
+
nested = BulletList(items=(ListItem((TextNode("child"),)),))
|
|
240
|
+
items = (ListItem((TextNode("parent"), nested)),)
|
|
241
|
+
out = emit((BulletList(items=items),))
|
|
242
|
+
assert out.count("<ul>") == 2
|
|
243
|
+
assert out.count("</ul>") == 2
|
|
244
|
+
assert "<p>parent</p>" in out
|
|
245
|
+
assert "<p>child</p>" in out
|
|
246
|
+
|
|
247
|
+
def test_nested_ordered_list(self) -> None:
|
|
248
|
+
nested = OrderedList(items=(ListItem((TextNode("sub"),)),))
|
|
249
|
+
items = (ListItem((TextNode("top"), nested)),)
|
|
250
|
+
out = emit((OrderedList(items=items),))
|
|
251
|
+
assert "<ol>" in out
|
|
252
|
+
assert "<ul>" not in out
|
|
253
|
+
assert "<p>top</p>" in out
|
|
254
|
+
assert "<p>sub</p>" in out
|
|
255
|
+
|
|
256
|
+
def test_nested_list_no_inline_prefix(self) -> None:
|
|
257
|
+
"""A list item with only a nested sub-list (no inline text) renders cleanly."""
|
|
258
|
+
nested = BulletList(items=(ListItem((TextNode("only child"),)),))
|
|
259
|
+
items = (ListItem((nested,)),)
|
|
260
|
+
out = emit((BulletList(items=items),))
|
|
261
|
+
assert out.count("<ul>") == 2
|
|
262
|
+
assert "<p>only child</p>" in out
|
|
263
|
+
|
|
238
264
|
|
|
239
265
|
class TestHorizontalRule:
|
|
240
266
|
def test_hr(self) -> None:
|
|
@@ -364,3 +364,61 @@ def test_unresolved_md_link_with_anchor_degrades_to_label():
|
|
|
364
364
|
xhtml = emit((Paragraph(children=(link,)),))
|
|
365
365
|
assert "Hello World" in xhtml
|
|
366
366
|
assert "hello-worl.md" not in xhtml
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def test_link_inside_admonition_resolves():
|
|
370
|
+
"""A relative .md link inside an admonition body must resolve to ac:link.
|
|
371
|
+
|
|
372
|
+
Regression: links inside admonitions were reported as showing raw label
|
|
373
|
+
text instead of Confluence hyperlinks when the target page was in a
|
|
374
|
+
different section (cross-section link).
|
|
375
|
+
"""
|
|
376
|
+
from mkdocs_to_confluence.emitter.xhtml import emit
|
|
377
|
+
from mkdocs_to_confluence.parser.markdown import parse
|
|
378
|
+
|
|
379
|
+
nav = _make_nav([("proposals/2026/authentication-procedures.md", "Auth Procedures")])
|
|
380
|
+
link_map = build_link_map(nav)
|
|
381
|
+
current_path = "architecture/identity/keycloak.md"
|
|
382
|
+
|
|
383
|
+
md = '!!! info "Related document"\n See [Auth Procedures](../../proposals/2026/authentication-procedures.md).\n'
|
|
384
|
+
nodes = parse(md)
|
|
385
|
+
resolved = resolve_internal_links(nodes, link_map, current_path)
|
|
386
|
+
xhtml = emit(resolved)
|
|
387
|
+
|
|
388
|
+
assert 'ri:content-title="Auth Procedures"' in xhtml
|
|
389
|
+
assert "authentication-procedures.md" not in xhtml
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def test_cross_section_link_resolves_when_full_nav_used():
|
|
393
|
+
"""build_link_map must include all nav pages so cross-section links resolve.
|
|
394
|
+
|
|
395
|
+
The root cause of the admonition link bug: when publishing with --section,
|
|
396
|
+
the link_map was built from the section subtree only. Cross-section targets
|
|
397
|
+
were missing and links degraded to label text.
|
|
398
|
+
"""
|
|
399
|
+
from mkdocs_to_confluence.emitter.xhtml import emit
|
|
400
|
+
from mkdocs_to_confluence.parser.markdown import parse
|
|
401
|
+
|
|
402
|
+
# Two separate sections
|
|
403
|
+
full_nav = _make_nav([
|
|
404
|
+
("section-a/page.md", "Page A"),
|
|
405
|
+
("section-b/target.md", "Target Page"),
|
|
406
|
+
])
|
|
407
|
+
section_a_nav = _make_nav([("section-a/page.md", "Page A")])
|
|
408
|
+
|
|
409
|
+
link_map_full = build_link_map(full_nav)
|
|
410
|
+
link_map_section_only = build_link_map(section_a_nav)
|
|
411
|
+
|
|
412
|
+
md = "See [Target Page](../section-b/target.md).\n"
|
|
413
|
+
nodes = parse(md)
|
|
414
|
+
|
|
415
|
+
# With full nav: link resolves
|
|
416
|
+
resolved_full = resolve_internal_links(nodes, link_map_full, "section-a/page.md")
|
|
417
|
+
xhtml_full = emit(resolved_full)
|
|
418
|
+
assert 'ri:content-title="Target Page"' in xhtml_full
|
|
419
|
+
|
|
420
|
+
# With section-only nav: link degrades to label text
|
|
421
|
+
resolved_section = resolve_internal_links(nodes, link_map_section_only, "section-a/page.md")
|
|
422
|
+
xhtml_section = emit(resolved_section)
|
|
423
|
+
assert "Target Page" in xhtml_section
|
|
424
|
+
assert "ri:content-title" not in xhtml_section
|
|
@@ -1021,6 +1021,49 @@ class TestListParsing:
|
|
|
1021
1021
|
)
|
|
1022
1022
|
assert "Continuation" in item_text or "First point" in item_text
|
|
1023
1023
|
|
|
1024
|
+
def test_nested_bullet_list(self) -> None:
|
|
1025
|
+
md = "- item 1\n - nested 1\n - nested 2\n- item 2\n"
|
|
1026
|
+
nodes = parse(md)
|
|
1027
|
+
bl = first(nodes, BulletList)
|
|
1028
|
+
assert isinstance(bl, BulletList)
|
|
1029
|
+
assert len(bl.items) == 2
|
|
1030
|
+
# First item must have a nested BulletList as a child.
|
|
1031
|
+
nested = next((c for c in bl.items[0].children if isinstance(c, BulletList)), None)
|
|
1032
|
+
assert nested is not None, "expected nested BulletList in first item"
|
|
1033
|
+
assert len(nested.items) == 2
|
|
1034
|
+
assert nested.items[0].children[0].text == "nested 1" # type: ignore[union-attr]
|
|
1035
|
+
assert nested.items[1].children[0].text == "nested 2" # type: ignore[union-attr]
|
|
1036
|
+
|
|
1037
|
+
def test_nested_ordered_list(self) -> None:
|
|
1038
|
+
md = "1. first\n 1. sub one\n 2. sub two\n2. second\n"
|
|
1039
|
+
nodes = parse(md)
|
|
1040
|
+
ol = first(nodes, OrderedList)
|
|
1041
|
+
assert isinstance(ol, OrderedList)
|
|
1042
|
+
nested = next((c for c in ol.items[0].children if isinstance(c, OrderedList)), None)
|
|
1043
|
+
assert nested is not None, "expected nested OrderedList in first item"
|
|
1044
|
+
assert len(nested.items) == 2
|
|
1045
|
+
|
|
1046
|
+
def test_deeply_nested_bullet_list(self) -> None:
|
|
1047
|
+
md = "- a\n - b\n - c\n"
|
|
1048
|
+
nodes = parse(md)
|
|
1049
|
+
bl = first(nodes, BulletList)
|
|
1050
|
+
assert isinstance(bl, BulletList)
|
|
1051
|
+
level2 = next((c for c in bl.items[0].children if isinstance(c, BulletList)), None)
|
|
1052
|
+
assert level2 is not None
|
|
1053
|
+
level3 = next((c for c in level2.items[0].children if isinstance(c, BulletList)), None)
|
|
1054
|
+
assert level3 is not None
|
|
1055
|
+
assert level3.items[0].children[0].text == "c" # type: ignore[union-attr]
|
|
1056
|
+
|
|
1057
|
+
def test_mixed_nested_list(self) -> None:
|
|
1058
|
+
"""Bullet list item containing an ordered sub-list."""
|
|
1059
|
+
md = "- item\n 1. sub one\n 2. sub two\n"
|
|
1060
|
+
nodes = parse(md)
|
|
1061
|
+
bl = first(nodes, BulletList)
|
|
1062
|
+
assert isinstance(bl, BulletList)
|
|
1063
|
+
nested_ol = next((c for c in bl.items[0].children if isinstance(c, OrderedList)), None)
|
|
1064
|
+
assert nested_ol is not None
|
|
1065
|
+
assert len(nested_ol.items) == 2
|
|
1066
|
+
|
|
1024
1067
|
|
|
1025
1068
|
# ── Definition list parsing ───────────────────────────────────────────────────
|
|
1026
1069
|
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/requires.txt
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs2confluence.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/emitter/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/ir/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/ir/document.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/ir/treeutil.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/config.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/extra_css.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/nav.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/loader/page.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/parser/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/pdf/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/pdf/generator.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/pdf/render.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/abbrevs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/fence.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preprocess/icons.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preview/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preview/render.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/preview/server.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/publisher/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/publisher/client.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/__init__.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/anchoring.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/command.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/comments.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/github.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/platform.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/sync/state.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/abbrevs.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/assets.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/footer.py
RENAMED
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/src/mkdocs_to_confluence/transforms/images.py
RENAMED
|
File without changes
|
|
File without changes
|
{mkdocs2confluence-0.10.0 → mkdocs2confluence-0.10.2}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|