from typing import Callable, Iterable, Self from bs4 import BeautifulSoup, Tag from elements import IdAttr, Renderable, Template, TemplateName from puppytype import ElementLike, ElementLikeList, Parsable, TemplateDict class Puppygirl: elements: list[Renderable] templates: TemplateDict def __init__(self, elements: list[Callable[[Self], Renderable]] = [], templates: ElementLikeList = []): self.templates = Puppygirl._create_template_dict(templates) self.elements = [self._instantiate(el) for el in elements] def _instantiate(self, value: Callable[[Self], Renderable] | Renderable) -> Renderable: if(isinstance(value, Callable)): return value(self) return value def _create_template_dict(templates: Iterable[ElementLike]) -> TemplateDict: templates = [Template(t) for t in templates] return {t[IdAttr]: t for t in templates} def add_template(self, template: ElementLike): template = Template(template) self._templates[template[IdAttr]] = template def _find_local_templates(tree: BeautifulSoup) -> TemplateDict: templates = tree.find_all(TemplateName) templates = filter(lambda t: t.has_attr(IdAttr), templates) return Puppygirl._create_template_dict(templates) def fetch(self, path: str) -> BeautifulSoup: with open(path, "r") as f: return self.parse(f) def parse(self, value: Parsable) -> BeautifulSoup: if isinstance(value, BeautifulSoup) or isinstance(value, Tag): return self.parse_tree(value) return self.parse_tree(BeautifulSoup(value, features='html.parser')) def parse_tree(self, tree: BeautifulSoup) -> BeautifulSoup: templates = Puppygirl._find_local_templates(tree) | self.templates for element in self.elements: if hasattr(element, "name"): for tag in tree.find_all(element.name): new_tag = element.render(tag, templates) if isinstance(new_tag, Iterable): tag.extend(new_tag) tag.unwrap() else: tag.replace_with(new_tag) return tree