64 lines
2.2 KiB
Python
64 lines
2.2 KiB
Python
"""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)
|