from __future__ import annotations from dataclasses import dataclass from typing import Any, Protocol @dataclass class PublishResult: mode: str status: str slug: str blog_url: str public_ok: bool = False error: str | None = None class BlogClient(Protocol): def create_post(self, payload: dict[str, Any]) -> dict[str, Any]: ... def publish_post(self, slug: str) -> None: ... def dry_run_publish(slug: str, base_url: str) -> PublishResult: return PublishResult( mode="dry-run", status="ok", slug=slug, blog_url=f"{base_url.rstrip('/')}/posts/{slug}", public_ok=True, ) def publish_markdown( *, title: str, markdown: str, tags: list[str], slug: str, base_url: str, mode: str, markdown_report: dict[str, Any], client: BlogClient | None, ) -> PublishResult: blocking_errors = markdown_report.get("blocking_errors", []) or [] blog_url = f"{base_url.rstrip('/')}/posts/{slug}" if blocking_errors: return PublishResult( mode=mode, status="blocked", slug=slug, blog_url=blog_url, public_ok=False, error=";".join(blocking_errors), ) if mode == "dry-run": return dry_run_publish(slug, base_url) if client is None: return PublishResult( mode=mode, status="failed", slug=slug, blog_url=blog_url, public_ok=False, error="missing_blog_client", ) payload = {"title": title, "content": markdown, "tags": tags, "slug": slug} try: create_resp = client.create_post(payload) created_slug = create_resp.get("slug") or slug if mode == "publish": client.publish_post(created_slug) return PublishResult( mode=mode, status="ok", slug=created_slug, blog_url=f"{base_url.rstrip('/')}/posts/{created_slug}", public_ok=mode == "publish", ) except Exception as exc: return PublishResult( mode=mode, status="failed", slug=slug, blog_url=blog_url, public_ok=False, error=f"{type(exc).__name__}: {exc}", )