from flask import Markup, escape from typing import List, Optional, Union, Tuple import types tag_names = ["a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "data", "datalist", "dd", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "head", "header", "hgroup", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "html", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "legend", "li", "link", "main", "map", "mark", "menu", "menuitem", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup", "svg", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"] class EscapeError(RuntimeError): pass def escape_attribute(x:str) -> str: return str(x).replace("&", "&").replace('"', """) def escape_attribute_name(x:str) -> str: for c in "<>='\" ": if c in x: raise EscapeError return x def escape_tag_name(x:str) -> str: return escape_attribute_name(x) Element = Union[str, Markup, 'Bucket'] def indent_str(indent, s): return " "*indent + s + "\n" if indent is not None else s class Bucket: content: List[Element] is_paired: bool builder: 'Builder' def __init__(self, builder): self.builder = builder self.is_paired = False self.content = [] self.before_tag = None def add(self, x: Element): if isinstance(x, types.FunctionType): before_current_tag = self.builder.current_tag self.builder.current_tag = self try: x(self) finally: self.builder.current_tag = before_current_tag else: self.is_paired = True self.content.append(x) def __call__(self, *arg, **kvarg): for i in arg: self.add(i) for k, v in kvarg.items(): self.add_attribute(remove_leading_underscore(k), v) return self def add_tag(self, name: str, attributes: List[Tuple[str, str]]): t = Tag(self.builder, name, attributes) self.add(t) return t def line(self): t = Line(self.builder) self.add(t) return t def _line(self): return Line(self) def bucket(self): t = Bucket(self.builder) self.add(t) return t def _bucket(self): return Bucket(self) def print(self): out = [] self.serialize_append_to_list(out, 0) return Markup("".join(out)) def print_file(self): out = ["\n"] self.serialize_append_to_list(out, 0) return Markup("".join(out)) def __enter__(self): if self.before_tag is not None: raise RuntimeError("Duplicit __enter__") self.before_tag = self.builder.current_tag self.builder.current_tag = self return self def __exit__(self, exc_type, exc_value, exc_traceback): if self.before_tag is None: raise RuntimeError("__exit__ before __enter__") self.builder.current_tag = self.before_tag self.before_tag = None def serialize_append_to_list(self, out, indent): for i in self.content: if isinstance(i, Bucket): i.serialize_append_to_list(out, indent) elif isinstance(i, Markup): for j in i.__html__().split("\n"): out.append(indent_str(indent, j)) else: for j in escape(str(i)).split("\n"): out.append(indent_str(indent, j)) class Tag(Bucket): name: str attributes: List[Tuple[str, str]] def __init__(self, builder, name: str, attributes: List[Tuple[str, str]]): super().__init__(builder) self.name = name self.attributes = attributes def add_attribute(self, k, v): self.attributes.append((k, v)) def format_attributes(self): return " ".join(f'{escape_attribute_name(i[0])}="{escape_attribute(i[1])}"' for i in self.attributes) def serialize_append_to_list(self, out, indent): if self.is_paired: out.append(indent_str(indent, f"<{escape_tag_name(self.name)} {self.format_attributes()}>")) super().serialize_append_to_list(out, indent + 1 if indent is not None else None) out.append(indent_str(indent, f"")) else: out.append(indent_str(indent, f"<{escape_tag_name(self.name)} {self.format_attributes()} />")) class Line(Bucket): def serialize_append_to_list(self, out, indent): if indent is None: super().serialize_append_to_list(out, None) else: out.append(indent_str(indent,"")[:-1]) super().serialize_append_to_list(out, None) out.append("\n") class Builder: current_tag: Bucket root_tag: Bucket def __init__(self, tag: Bucket = None): if tag is None: tag = Bucket(self) self.root_tag = tag self.current_tag = tag def add_tag(self, name: str, attributes: List[Tuple[str, str]] = []): return self.current_tag.add_tag(name, attributes) def line(self): return self.current_tag.line() def _line(self): return Line(self) def bucket(self): return self.current_tag.bucket() def _bucket(self): return Bucket(self) def __call__(self, *arg, **kvarg): self.current_tag(*arg, **kvarg) return self def print(self): return self.root_tag.print() def print_file(self): return self.root_tag.print_file() for name in tag_names: def run(name): def l1(self, *args, **kvarg): return self.add_tag(name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()])(*args) def l2(self, *args, **kvarg): return Tag(self, name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()])(*args) def l3(self, *args, **kvarg): return Tag(self.builder, name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()])(*args) setattr(Builder, name, l1) setattr(Builder, f"_{name}", l2) setattr(Bucket, name, l1) setattr(Bucket, f"_{name}", l3) run(name) class HtmlBuilder(Builder): def __init__(self): super().__init__(Tag(self, "html", [])) def remove_leading_underscore(s): if s == "": return s if s[0] == "_": return s[1:] return s class WrapAfterBuilder(Builder): def __init__(self, f): super().__init__(Bucket(self)) self.wrap_done = False self.wrap_function = f def wrap(self, *arg, **kvarg): if self.wrap_done: return self.wrap_done = True content = self.root_tag.content self.root_tag = None self.current_tag = None self.root_tag = self.wrap_function(self, content, *arg, **kvarg) or self.root_tag return self def print(self, *arg, **kvarg): self.wrap() return super().print(*arg, **kvarg) def print_file(self, *arg, **kvarg): self.wrap() return super().print_file(*arg, **kvarg) def WrapAfterBuilder_decorator(f): def l(): return WrapAfterBuilder(f) return l