"""Shared helpers for templateify services (daily + special events).""" import logging from typing import Callable, Dict, List from bs4 import BeautifulSoup from bs4.element import NavigableString, Tag logger = logging.getLogger(__name__) class TemplateifyHelpers: """Holds soup/tokens state and provides shared templateify operations.""" def __init__(self, soup: BeautifulSoup) -> None: self.soup = soup self.tokens: List[Dict[str, str]] = [] self._seen: set = set() def register(self, name: str, description: str) -> None: if name not in self._seen: self.tokens.append({"name": name, "description": description}) self._seen.add(name) def set_text(self, selector: str, token: str, description: str) -> None: node = self.soup.select_one(selector) if not node: return node.clear() node.append(token) self.register(token, description) def set_attr(self, parent: Tag, selector: str, attr: str, token: str, description: str) -> None: node = parent.select_one(selector) if not node: return node[attr] = token self.register(token, description) def set_child_text(self, parent: Tag, selector: str, token: str, description: str) -> None: node = parent.select_one(selector) if not node: return node.clear() node.append(token) self.register(token, description) def loopify(self, selector: str, loop_name: str, transformer: Callable[[Tag], None], description: str) -> None: nodes = self.soup.select(selector) if not nodes: return template_node = nodes[0] for extra in nodes[1:]: extra.decompose() transformer(template_node) self.register(loop_name, description) # Determine which element to wrap (the node, or its parent anchor) if template_node.parent and template_node.parent.name == "a": target = template_node.parent else: target = template_node parent = target.parent if not parent: logger.warning(f"Template node {selector} has no parent, cannot wrap in loop") return try: idx = parent.index(target) parent.insert(idx, NavigableString(f"{{{{#{loop_name}}}}}")) parent.insert(idx + 2, NavigableString(f"{{{{/{loop_name}}}}}")) except ValueError: logger.warning(f"Could not find index for {selector}, node may have been moved") def wrap_images_in_anchors(self, header_alt: str = "Polygraph by Polymarket", footer_alt: str = "Polymarket", link_url: str = "https://poly.market/0d7VvzI") -> None: """Wrap header and footer logo images in anchor tags if not already linked.""" for alt_text in (header_alt, footer_alt): for table in self.soup.find_all("table", role="presentation"): img = table.find("img", alt=alt_text) if img and not img.find_parent("a"): link = self.soup.new_tag("a", href=link_url, target="_blank", rel="noopener noreferrer") img.wrap(link) break