from typing import List, Tuple, Union, Optional from css import Chainer, Writer from dataclasses import dataclass Attributes = dict[str, Union[str, bool, None]] Stringy = Union[str, Tuple[str, ...]] Renderable = Union[str, 'Branch', 'Leaf'] TAGGED = Union[Tuple[Chainer], Chainer] def MakeAttrs(css:TAGGED, id:Optional[str], attributes: Attributes) -> str: if not attributes: attributes = {} if css: attributes['class'] = " ".join(css) if isinstance(css, tuple) else css if id: attributes['id'] = id attrList: List[str] = [] for key, value in attributes.items(): if value: attrList.append(f'{key}="{value}"') return " " + " ".join(attrList) class Leaf(): name:str = "" def __init__(self, name:Optional[str] = None): if name: self.name = name self.attrs = None def props(self, css:Optional[TAGGED], id:Optional[str], attributes: Attributes): clone = self.__class__(self.name) clone.attrs = MakeAttrs(css, id, attributes) return clone def __call__(self, css:Optional[TAGGED] = None, id:Optional[str] = None): return self.props(css, id, {}) @classmethod def template(cls, name:str, attrs:Optional[str] = None): return f'<{name}{attrs or ""}/>' def __repr__(self) -> str: return __class__.template(self.name, self.attrs) class Branch(Leaf): def __init__(self, name:str): self.name = name self.attrs = None @classmethod def template(cls, name:str, attrs:Optional[str] = None, children:Optional[str] = None): return f'<{name}{attrs or ""}>\n{children}\n' def __repr__(self) -> str: return self.__class__.template(self.name, self.attrs) def __getitem__(self, key:Union[Renderable, Tuple[Renderable, ...]]) -> Renderable: children = f'\n'.join(str(k) for k in key) if isinstance(key, tuple) else str(key) return self.__class__.template(self.name, self.attrs, children) class IMGTag(Leaf): def __call__(self, css:Optional[TAGGED] = None, id:Optional[str] = None, src:Optional[str] = None): return self.props(css, id, {"src":src}) class ATag(Branch): def __call__(self, css:Optional[TAGGED] = None, id:Optional[str] = None, href:Optional[str] = None, target:Optional[str] = None): return self.props(css, id, {"href":href, "target":target}) class HTMLTag(Branch): @classmethod def template(cls, name:str, attrs:Optional[str] = None, children:Optional[str] = None): return f'<{name}{attrs or ""}>\n{children}\n' IMG = IMGTag("img") A = ATag("a") BR = Leaf("br") HR = Leaf("hr") DIV = Branch("div") P = Branch("p") SPAN = Branch("span") EM = Branch("EM") H1 = Branch("h1") H2 = Branch("h2") H3 = Branch("h3") H4 = Branch("h4") SECTION = Branch("section") MAIN = Branch("main") HTML = HTMLTag("html") HEAD = Branch("head") BODY = Branch("body") STYLE = Branch("style") @dataclass class Capture: def __init__(self, html:Renderable): self.html = html self.css = Writer.dump()