puppygirl-py/puppygirl/puppygirl.py
2025-10-06 06:31:11 -04:00

56 lines
2 KiB
Python

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