"""funnies cartoon + comic selection. Per functional design: BR2.1 (search the real xkcd site at build time, embed locally), BR2.2 (tasteful content-derived fallback when no cartoon found), BR3.1 (content-enrichment comics). A build-time internet fetch is allowed to enrich content; the EMITTED page stays self-contained (NFR4) because fetched assets are embedded as a local ref, never a remote URL at print. """ from __future__ import annotations from typing import Protocol from .model import Cartoon, Comic class CartoonSource(Protocol): """Injectably fetches/selects a cartoon from an enrichment source.""" def fetch(self, context: str) -> Cartoon | None: ... class StubXkcdCartoonSource: """Deterministic stub (tests / offline): returns a local-bundled cartoon.""" def __init__(self, found: bool = True) -> None: self.found = found def fetch(self, context: str) -> Cartoon | None: if not self.found: return None return Cartoon( cartoon_id="xkcd-1", source="xkcd", art_ref="assets/xkcd-1.svg", # local/bundled — never remote caption="A cartoon tied to the occasion", ) def tasteful_fallback(context: str, cartoon_id: str = "fallback-1") -> Cartoon: """BR2.2: a tasteful content-derived strip when no cartoon is found. Returns a locally-derived placeholder (never a remote URL at print). """ return Cartoon( cartoon_id=cartoon_id, source="generated", art_ref="assets/fallback-strip.svg", caption=f"A content-derived strip for: {context[:60]}", ) def select_cartoon(context: str, source: CartoonSource | None = None) -> Cartoon: """BR2.1/BR2.2: fetch an xkcd cartoon, falling back to a tasteful strip.""" src = source or StubXkcdCartoonSource(found=True) cartoon = src.fetch(context) return cartoon if cartoon else tasteful_fallback(context) def build_comic(comic_id: str, source: str, art_ref: str) -> Comic: """BR3.1: a comic strip placed in the funnies section (overridable per article). The caller supplies a local/bundled art ref.""" return Comic(comic_id=comic_id, source=source, art_ref=art_ref)