mkdocs2confluence 0.7.26__tar.gz → 0.7.28__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 (72) hide show
  1. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/PKG-INFO +1 -1
  2. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/pyproject.toml +1 -1
  3. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs2confluence.egg-info/PKG-INFO +1 -1
  4. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/publisher/client.py +6 -7
  5. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/publisher/pipeline.py +9 -6
  6. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_publish_pipeline.py +97 -0
  7. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/LICENSE +0 -0
  8. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/README.md +0 -0
  9. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/setup.cfg +0 -0
  10. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs2confluence.egg-info/SOURCES.txt +0 -0
  11. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs2confluence.egg-info/dependency_links.txt +0 -0
  12. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs2confluence.egg-info/entry_points.txt +0 -0
  13. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs2confluence.egg-info/requires.txt +0 -0
  14. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs2confluence.egg-info/top_level.txt +0 -0
  15. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/__init__.py +0 -0
  16. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/cli.py +0 -0
  17. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/emitter/__init__.py +0 -0
  18. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/emitter/xhtml.py +0 -0
  19. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/ir/__init__.py +0 -0
  20. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/ir/document.py +0 -0
  21. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/ir/nodes.py +0 -0
  22. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/ir/treeutil.py +0 -0
  23. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/loader/__init__.py +0 -0
  24. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/loader/config.py +0 -0
  25. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/loader/extra_css.py +0 -0
  26. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/loader/nav.py +0 -0
  27. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/loader/page.py +0 -0
  28. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/parser/__init__.py +0 -0
  29. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/parser/markdown.py +0 -0
  30. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/pdf/__init__.py +0 -0
  31. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/pdf/generator.py +0 -0
  32. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/pdf/render.py +0 -0
  33. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preprocess/__init__.py +0 -0
  34. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preprocess/abbrevs.py +0 -0
  35. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preprocess/fence.py +0 -0
  36. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preprocess/frontmatter.py +0 -0
  37. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preprocess/icons.py +0 -0
  38. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preprocess/includes.py +0 -0
  39. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preprocess/linkdefs.py +0 -0
  40. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preview/__init__.py +0 -0
  41. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preview/render.py +0 -0
  42. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/preview/server.py +0 -0
  43. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/publisher/__init__.py +0 -0
  44. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/transforms/__init__.py +0 -0
  45. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/transforms/abbrevs.py +0 -0
  46. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/transforms/assets.py +0 -0
  47. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/transforms/editlink.py +0 -0
  48. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/transforms/images.py +0 -0
  49. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/transforms/internallinks.py +0 -0
  50. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/src/mkdocs_to_confluence/transforms/mermaid.py +0 -0
  51. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_abbrevs.py +0 -0
  52. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_cli.py +0 -0
  53. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_editlink.py +0 -0
  54. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_emitter.py +0 -0
  55. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_extra_css.py +0 -0
  56. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_frontmatter.py +0 -0
  57. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_icons.py +0 -0
  58. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_images.py +0 -0
  59. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_internallinks.py +0 -0
  60. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_ir.py +0 -0
  61. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_linkdefs.py +0 -0
  62. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_loader.py +0 -0
  63. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_mermaid.py +0 -0
  64. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_page_loader.py +0 -0
  65. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_parser.py +0 -0
  66. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_pdf.py +0 -0
  67. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_preprocess.py +0 -0
  68. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_preview.py +0 -0
  69. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_publish_client.py +0 -0
  70. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_publish_config.py +0 -0
  71. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_server.py +0 -0
  72. {mkdocs2confluence-0.7.26 → mkdocs2confluence-0.7.28}/tests/test_treeutil.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs2confluence
3
- Version: 0.7.26
3
+ Version: 0.7.28
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.7.26"
3
+ version = "0.7.28"
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.7.26
3
+ Version: 0.7.28
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
@@ -371,7 +371,6 @@ class ConfluenceClient:
371
371
  Sends ``{id, name, color}`` when a matching space state is found, otherwise
372
372
  falls back to ``{name, color}`` with a sensible default colour.
373
373
  """
374
- import sys as _sys
375
374
  def _normalize(s: str) -> str:
376
375
  return s.lower().replace("-", " ").strip()
377
376
 
@@ -383,7 +382,6 @@ class ConfluenceClient:
383
382
  self._space_states[cache_key] = self._fetch_available_states(page_id)
384
383
  print(
385
384
  f" [status] space states fetched: {[s.get('name') for s in self._space_states[cache_key]]}",
386
- file=_sys.stderr,
387
385
  )
388
386
 
389
387
  matched: dict[str, Any] | None = None
@@ -398,7 +396,7 @@ class ConfluenceClient:
398
396
  "name": matched["name"],
399
397
  "color": matched["color"],
400
398
  }
401
- print(f" [status] matched space state: {body}", file=_sys.stderr)
399
+ print(f" [status] matched space state: {body}")
402
400
  else:
403
401
  # Fall back: name + color (required together when no id)
404
402
  _default_colors = {
@@ -410,13 +408,13 @@ class ConfluenceClient:
410
408
  "outdated": "#ff7452",
411
409
  }
412
410
  body = {"name": name, "color": _default_colors.get(_normalize(name), "#2684ff")}
413
- print(f" [status] no space state match — using fallback: {body}", file=_sys.stderr)
411
+ print(f" [status] no space state match — using fallback: {body}")
414
412
 
415
413
  url = self._v1(f"/content/{page_id}/state")
416
414
  resp = self._http.put(url, json=body)
417
- print(f" [status] PUT {url} → HTTP {resp.status_code}", file=_sys.stderr)
415
+ print(f" [status] PUT {url} → HTTP {resp.status_code}")
418
416
  if not resp.is_success:
419
- print(f" [status] response body: {resp.text[:300]}", file=_sys.stderr)
417
+ print(f" [status] response body: {resp.text[:300]}")
420
418
  self._raise_for_status(resp, f"set_page_status({page_id!r}, {status_key!r})")
421
419
 
422
420
  def _fetch_available_states(self, page_id: str) -> list[dict[str, Any]]:
@@ -429,7 +427,8 @@ class ConfluenceClient:
429
427
  resp = self._http.get(self._v1(f"/content/{page_id}/state/available"))
430
428
  if resp.is_success:
431
429
  data: dict[str, Any] = resp.json()
432
- return data.get("spaceContentStates") or []
430
+ return list(data.get("spaceContentStates") or [])
431
+ print(f" [warn] could not fetch available states (HTTP {resp.status_code}) — using fallback")
433
432
  return []
434
433
 
435
434
  def list_attachments(self, page_id: str) -> dict[str, dict[str, Any]]:
@@ -277,6 +277,8 @@ def _plan_nodes(
277
277
  xhtml, attachments, labels, confluence_status = compile_page(
278
278
  index_child, config, link_map, quiet=quiet
279
279
  )
280
+ if confluence_status:
281
+ print(f" [debug] '{clean_title}' (index) has frontmatter status: {confluence_status!r}")
280
282
  existing = client.find_page(space_id, clean_title)
281
283
  xhtml_h = _xhtml_hash(xhtml)
282
284
  if existing is not None and client.get_content_hash(str(existing["id"])) == xhtml_h:
@@ -388,6 +390,9 @@ def _plan_nodes(
388
390
  )
389
391
  continue
390
392
 
393
+ if confluence_status:
394
+ print(f" [debug] '{clean_title}' has frontmatter status: {confluence_status!r}")
395
+
391
396
  existing = client.find_page(space_id, clean_title)
392
397
  xhtml_h = _xhtml_hash(xhtml)
393
398
  if existing is not None and client.get_content_hash(str(existing["id"])) == xhtml_h:
@@ -664,12 +669,12 @@ def _post_process_action(
664
669
  # Set Confluence page status (rough-draft / in-progress / etc.) — non-fatal.
665
670
  if action.page_id and action.confluence_status and not action.is_folder:
666
671
  try:
667
- print(f" [status] setting '{action.confluence_status}' on page {action.page_id!r}...", file=sys.stderr)
672
+ print(f" [status] setting '{action.confluence_status}' on page {action.page_id!r}...")
668
673
  client.set_page_status(action.page_id, action.confluence_status, space_key=space_key)
669
- print(" [status] ok", file=sys.stderr)
674
+ print(" [status] ok")
670
675
  except Exception as exc:
671
676
  # Always print status errors — user configured status explicitly
672
- print(f" [warn] could not set page status '{action.confluence_status}': {exc}", file=sys.stderr)
677
+ print(f" [warn] could not set page status '{action.confluence_status}': {exc}")
673
678
 
674
679
  # Upload assets — skip files whose mtime is not newer than Confluence.
675
680
  if action.page_id and action.attachments:
@@ -734,14 +739,12 @@ def execute_publish(
734
739
  try:
735
740
  print(
736
741
  f" [status] setting '{action.confluence_status}' on page {action.page_id!r}...",
737
- file=sys.stderr,
738
742
  )
739
743
  client.set_page_status(action.page_id, action.confluence_status, space_key=space_key)
740
- print(" [status] ok", file=sys.stderr)
744
+ print(" [status] ok")
741
745
  except Exception as exc:
742
746
  print(
743
747
  f" [warn] could not set page status '{action.confluence_status}': {exc}",
744
- file=sys.stderr,
745
748
  )
746
749
  continue
747
750
 
@@ -1945,3 +1945,100 @@ def test_execute_publish_quiet_suppresses_stdout(tmp_path: Path, capsys: pytest.
1945
1945
 
1946
1946
  out, _ = capsys.readouterr()
1947
1947
  assert out == ""
1948
+
1949
+
1950
+ # ── Status: end-to-end flow ───────────────────────────────────────────────────
1951
+
1952
+
1953
+ def test_compile_page_returns_confluence_status(tmp_path: Path) -> None:
1954
+ """compile_page must return the status: value from front matter."""
1955
+ docs = tmp_path / "docs"
1956
+ docs.mkdir()
1957
+ md = docs / "page.md"
1958
+ md.write_text("---\nstatus: in-progress\n---\n\n# My Page\n\nContent.\n", encoding="utf-8")
1959
+
1960
+ node = _page_node("My Page", md)
1961
+ config = _make_config(docs)
1962
+ _, _, _, confluence_status = compile_page(node, config)
1963
+
1964
+ assert confluence_status == "in-progress"
1965
+
1966
+
1967
+ def test_plan_publish_sets_confluence_status_on_create(tmp_path: Path) -> None:
1968
+ """plan_publish must carry confluence_status into a create PageAction."""
1969
+ docs = tmp_path / "docs"
1970
+ docs.mkdir()
1971
+ md = docs / "page.md"
1972
+ md.write_text("---\nstatus: in-progress\n---\n\n# My Page\n\nContent.\n", encoding="utf-8")
1973
+
1974
+ node = _page_node("My Page", md)
1975
+ config = _make_config(docs)
1976
+ conf_config = _make_conf_config()
1977
+
1978
+ client = MagicMock()
1979
+ client.find_page.return_value = None
1980
+
1981
+ plan = plan_publish([node], client, config, conf_config, space_id="42")
1982
+
1983
+ assert plan[0].action == "create"
1984
+ assert plan[0].confluence_status == "in-progress"
1985
+
1986
+
1987
+ def test_plan_publish_sets_confluence_status_on_skip(tmp_path: Path) -> None:
1988
+ """plan_publish must carry confluence_status into a skip (unchanged) PageAction."""
1989
+ from mkdocs_to_confluence.publisher.pipeline import _xhtml_hash
1990
+
1991
+ docs = tmp_path / "docs"
1992
+ docs.mkdir()
1993
+ md = docs / "page.md"
1994
+ md.write_text("---\nstatus: in-progress\n---\n\n# My Page\n\nContent.\n", encoding="utf-8")
1995
+
1996
+ node = _page_node("My Page", md)
1997
+ config = _make_config(docs)
1998
+ conf_config = _make_conf_config()
1999
+
2000
+ xhtml, _, _, _ = compile_page(node, config)
2001
+ stored_hash = _xhtml_hash(xhtml)
2002
+
2003
+ existing_page = {"id": "77", "version": {"number": 2}}
2004
+ client = MagicMock()
2005
+ client.find_page.return_value = existing_page
2006
+ client.get_content_hash.return_value = stored_hash
2007
+
2008
+ plan = plan_publish([node], client, config, conf_config, space_id="42")
2009
+
2010
+ assert plan[0].action == "skip"
2011
+ assert plan[0].confluence_status == "in-progress"
2012
+
2013
+
2014
+ def test_execute_publish_calls_set_page_status_on_create(tmp_path: Path) -> None:
2015
+ """execute_publish must call client.set_page_status for a created page with confluence_status."""
2016
+ from mkdocs_to_confluence.publisher.pipeline import execute_publish
2017
+
2018
+ node = _make_section_node("P", [])
2019
+ action = PageAction(
2020
+ node=node, title="P", action="create",
2021
+ parent_id=None, xhtml="<p/>", confluence_status="in-progress",
2022
+ )
2023
+ client = MagicMock()
2024
+ client.create_page.return_value = {"id": "99"}
2025
+
2026
+ execute_publish([action], client, space_id="42", docs_dir=tmp_path)
2027
+
2028
+ client.set_page_status.assert_called_once_with("99", "in-progress", space_key=None)
2029
+
2030
+
2031
+ def test_execute_publish_calls_set_page_status_on_skip(tmp_path: Path) -> None:
2032
+ """execute_publish must call client.set_page_status even for skipped (unchanged) pages."""
2033
+ from mkdocs_to_confluence.publisher.pipeline import execute_publish
2034
+
2035
+ node = _make_section_node("P", [])
2036
+ action = PageAction(
2037
+ node=node, title="P", action="skip",
2038
+ parent_id=None, page_id="88", confluence_status="in-progress",
2039
+ )
2040
+ client = MagicMock()
2041
+
2042
+ execute_publish([action], client, space_id="42", docs_dir=tmp_path)
2043
+
2044
+ client.set_page_status.assert_called_once_with("88", "in-progress", space_key=None)