You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

177 lines
5.9 KiB

from flask import Markup, escape
from typing import List, Optional, Union, Tuple
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 x.replace("&", "&ampersand;").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, 'Tag']
class Tag:
name: str
attributes: List[Tuple[str, str]]
content: List[Element]
is_paired: bool
builder: 'Builder'
def __init__(self, builder, name: str, attributes: List[Tuple[str, str]]):
self.builder = builder
self.name = name
self.attributes = attributes
self.is_paired = False
self.content = []
self.before_tag = None
def add(self, x: Element):
self.is_paired = True
self.content.append(x)
def add_attribute(k, v):
self.attributes.append((k, v))
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 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):
indent_str = " "
if self.is_paired:
out.append(indent_str*indent + f"<{escape_tag_name(self.name)} {self.format_attributes()}>\n")
indent += 1
for i in self.content:
if isinstance(i, Tag):
i.serialize_append_to_list(out, indent)
elif isinstance(i, Markup):
for j in i.__html__().split("\n"):
out.append(indent_str*indent + j + "\n")
else:
for j in escape(str(i)).split("\n"):
out.append(indent_str*indent + j + "\n")
indent -= 1
out.append(indent_str*indent + f"</{escape_tag_name(self.name)}>\n")
else:
out.append(indent_str*indent + f"<{escape_tag_name(self.name)} {self.format_attributes()} \>\n")
def print(self):
out = []
self.serialize_append_to_list(out, 0)
return Markup("".join(out))
def print_file(self):
out = ["<!DOCTYPE html>\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
class Builder:
_current_tag: Tag
_root_tag: Tag
def __init__(self, tag: Tag):
self._root_tag = tag
self._current_tag = tag
def _tag(self, name: str, attributes: List[Tuple[str, str]] = []):
return self._current_tag.add_tag(name, attributes)
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 l(self, **kvarg):
return self._tag(name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()])
setattr(Builder, name, l)
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__(Tag(self, "root", []))
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
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