Source code for paper_firehose.commands.topic_cmd
"""Topic management CLI subcommands."""
from __future__ import annotations
import logging
import re
from pathlib import Path
from typing import Any, Dict, List, Optional
import yaml
from ..core.config import ConfigManager, _DEFAULT_TOPIC_TEMPLATE
logger = logging.getLogger(__name__)
_VALID_NAME = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_-]*$")
[docs]
def list_topics(config_path: str) -> List[Dict[str, str]]:
"""Return available topics with names and descriptions."""
cfg = ConfigManager(config_path)
topics = cfg.get_available_topics()
result = []
for t in sorted(topics):
try:
tcfg = cfg.load_topic_config(t)
result.append({
"key": t,
"name": tcfg.get("name", t),
"description": tcfg.get("description", ""),
})
except Exception:
result.append({"key": t, "name": t, "description": "(failed to load)"})
return result
[docs]
def show_topic(config_path: str, name: str) -> str:
"""Return a topic config as formatted YAML."""
cfg = ConfigManager(config_path)
tcfg = cfg.load_topic_config(name)
return yaml.safe_dump(tcfg, default_flow_style=False, sort_keys=False)
[docs]
def add_topic(
config_path: str,
name: str,
*,
from_topic: Optional[str] = None,
) -> Path:
"""Create a new topic config file.
Args:
config_path: Path to the main config file.
name: New topic name (used as filename and ``name:`` field).
from_topic: Clone an existing topic instead of using the default template.
Returns:
Path to the created file.
Raises:
ValueError: If the name is invalid or the topic already exists.
"""
if not _VALID_NAME.match(name):
raise ValueError(
f"Invalid topic name '{name}': must be alphanumeric with hyphens/underscores"
)
cfg = ConfigManager(config_path)
topics_dir = Path(cfg.base_dir) / "topics"
topics_dir.mkdir(parents=True, exist_ok=True)
dest = topics_dir / f"{name}.yaml"
if dest.exists():
raise ValueError(f"Topic '{name}' already exists at {dest}")
if from_topic:
source_cfg = cfg.load_topic_config(from_topic)
data = dict(source_cfg)
data["name"] = name
content = yaml.safe_dump(data, default_flow_style=False, sort_keys=False)
else:
content = _DEFAULT_TOPIC_TEMPLATE.strip().replace('name: "example"', f'name: "{name}"')
dest.write_text(content + "\n", encoding="utf-8")
logger.info("Created topic '%s' at %s", name, dest)
return dest