feat(ses): add SES light novel pipeline; extract shared lib/wiki.py
New pipeline at pipelines/ses/ publishes mikkeli/ses-light-novel to the SES: MediaWiki namespace. All pages get Category:SES and Category:SES:<type> (type from frontmatter, fallback to parent dir name). Shared functions (connect_wiki, markdown_to_wikitext, strip_first_h1, AUTO_BANNER_PREFIX) extracted from songs/publish.py into lib/wiki.py; songs refactored to import from there. Also adds publish-ses.yml workflow stub and updates Dockerfile and root README. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,6 @@ import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
@@ -13,6 +12,9 @@ from pathlib import Path
|
||||
import frontmatter
|
||||
import mwclient
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
||||
from lib.wiki import AUTO_BANNER_PREFIX, connect_wiki, markdown_to_wikitext, strip_first_h1
|
||||
|
||||
|
||||
def validate(fm: dict, path: Path) -> None:
|
||||
errors = []
|
||||
@@ -47,30 +49,6 @@ def validate(fm: dict, path: Path) -> None:
|
||||
raise ValueError(f"{path}: " + "; ".join(errors))
|
||||
|
||||
|
||||
def strip_first_h1(text: str) -> str:
|
||||
"""Remove the first '# Heading' line and any immediately following blank line."""
|
||||
lines = text.split("\n")
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith("# "):
|
||||
del lines[i]
|
||||
if i < len(lines) and lines[i].strip() == "":
|
||||
del lines[i]
|
||||
break
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def markdown_to_wikitext(body: str) -> str:
|
||||
result = subprocess.run(
|
||||
["pandoc", "-f", "markdown", "-t", "mediawiki"],
|
||||
input=body,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"pandoc failed: {result.stderr.strip()}")
|
||||
return result.stdout
|
||||
|
||||
|
||||
def build_sibling_map(posts: dict) -> dict[str, str]:
|
||||
"""Map abs_path_str → wiki_title for all published posts."""
|
||||
return {str(path): str(post.metadata["title"]) for path, post in posts.items()}
|
||||
@@ -115,19 +93,6 @@ def build_wikitext(fm: dict, body_wikitext: str, source_url: str, source_ref: st
|
||||
return f"{banner}\n\n{body_wikitext}{lrc_line}{categories}"
|
||||
|
||||
|
||||
def connect_wiki() -> mwclient.Site:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
api_url = os.environ["WIKI_API_URL"]
|
||||
parsed = urlparse(api_url)
|
||||
host = parsed.netloc
|
||||
# Strip api.php to get the wiki root path (e.g. "/" or "/w/")
|
||||
path = parsed.path[: parsed.path.rfind("/") + 1] or "/"
|
||||
site = mwclient.Site(host, path=path, scheme=parsed.scheme)
|
||||
site.login(os.environ["WIKI_BOT_USER"], os.environ["WIKI_BOT_PASSWORD"])
|
||||
return site
|
||||
|
||||
|
||||
def upload_lrc(lrc_path: Path, title: str, site: mwclient.Site) -> bool:
|
||||
"""Upload LRC file to wiki if content has changed. Returns True if uploaded."""
|
||||
if not lrc_path.exists():
|
||||
@@ -156,9 +121,6 @@ def upload_lrc(lrc_path: Path, title: str, site: mwclient.Site) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
AUTO_BANNER_PREFIX = "{{Auto-generated"
|
||||
|
||||
|
||||
def publish_one(abs_path: Path, source_dir: Path, post, site: mwclient.Site, source_ref: str, gitea_repo_url: str, sibling_map: dict[str, str]) -> str:
|
||||
"""Returns 'published', 'noop', or 'skipped-manual'."""
|
||||
rel = abs_path.relative_to(source_dir)
|
||||
|
||||
Reference in New Issue
Block a user