diff --git a/adapters/atlassian/confluence.py b/adapters/atlassian/confluence.py new file mode 100644 index 0000000..a7e37f8 --- /dev/null +++ b/adapters/atlassian/confluence.py @@ -0,0 +1,254 @@ +"""Confluence Cloud adapter — creates pages via the Confluence REST API v2. + +Templates are authored in Markdown (single source of truth). +This adapter converts Markdown to Confluence Storage Format (XHTML) before uploading. +Authentication: email + ATLASSIAN_API_TOKEN env var (Basic auth). +API reference: https://developer.atlassian.com/cloud/confluence/rest/v2/ +""" + +from __future__ import annotations + +import os +import re +from typing import Any + +import requests + +from src.core.config import CoreConfig + + +def md_to_confluence_storage(text: str) -> str: + """Convert a subset of Markdown to Confluence Storage Format (XHTML). + + Covers the constructs used in qms-kit templates: + headings, bold, italic, tables, lists, blockquotes, HR, inline code, links. + """ + lines = text.splitlines() + out: list[str] = [] + i = 0 + + while i < len(lines): + line = lines[i] + + # Headings: # →

, ## →

, up to h6 + heading = re.match(r"^(#{1,6})\s+(.*)", line) + if heading: + level = len(heading.group(1)) + out.append(f"{_cf_inline(heading.group(2).strip())}") + i += 1 + continue + + # Horizontal rule + if re.match(r"^(-{3,}|\*{3,}|_{3,})\s*$", line): + out.append("
") + i += 1 + continue + + # Table: collect all consecutive table lines and render together + if line.strip().startswith("|"): + table_lines: list[str] = [] + while i < len(lines) and lines[i].strip().startswith("|"): + table_lines.append(lines[i]) + i += 1 + out.append(_cf_table(table_lines)) + continue + + # Unordered list: collect consecutive items into one