|
@ -1,5 +1,6 @@ |
|
|
from flask import Markup, escape |
|
|
from flask import Markup, escape |
|
|
from typing import List, Optional, Union, Tuple |
|
|
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"] |
|
|
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"] |
|
@ -23,30 +24,33 @@ def escape_attribute_name(x:str) -> str: |
|
|
def escape_tag_name(x:str) -> str: |
|
|
def escape_tag_name(x:str) -> str: |
|
|
return escape_attribute_name(x) |
|
|
return escape_attribute_name(x) |
|
|
|
|
|
|
|
|
Element = Union[str, Markup, 'Tag'] |
|
|
Element = Union[str, Markup, 'Bucket'] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Tag: |
|
|
def indent_str(indent, s): |
|
|
name: str |
|
|
return " "*indent + s + "\n" if indent is not None else s |
|
|
attributes: List[Tuple[str, str]] |
|
|
|
|
|
|
|
|
class Bucket: |
|
|
content: List[Element] |
|
|
content: List[Element] |
|
|
is_paired: bool |
|
|
is_paired: bool |
|
|
builder: 'Builder' |
|
|
builder: 'Builder' |
|
|
|
|
|
def __init__(self, builder): |
|
|
def __init__(self, builder, name: str, attributes: List[Tuple[str, str]]): |
|
|
|
|
|
self.builder = builder |
|
|
self.builder = builder |
|
|
self.name = name |
|
|
|
|
|
self.attributes = attributes |
|
|
|
|
|
self.is_paired = False |
|
|
self.is_paired = False |
|
|
self.content = [] |
|
|
self.content = [] |
|
|
self.before_tag = None |
|
|
self.before_tag = None |
|
|
|
|
|
|
|
|
def add(self, x: Element): |
|
|
def add(self, x: Element): |
|
|
self.is_paired = True |
|
|
if isinstance(x, types.FunctionType): |
|
|
self.content.append(x) |
|
|
before_current_tag = self.builder.current_tag |
|
|
|
|
|
self.builder.current_tag = self |
|
|
def add_attribute(k, v): |
|
|
try: |
|
|
self.attributes.append((k, v)) |
|
|
x(self) |
|
|
|
|
|
finally: |
|
|
|
|
|
self.builder.current_tag = before_current_tag |
|
|
|
|
|
else: |
|
|
|
|
|
self.is_paired = True |
|
|
|
|
|
self.content.append(x) |
|
|
|
|
|
|
|
|
def __call__(self, *arg, **kvarg): |
|
|
def __call__(self, *arg, **kvarg): |
|
|
for i in arg: |
|
|
for i in arg: |
|
@ -60,27 +64,13 @@ class Tag: |
|
|
self.add(t) |
|
|
self.add(t) |
|
|
return t |
|
|
return t |
|
|
|
|
|
|
|
|
def format_attributes(self): |
|
|
def line(self): |
|
|
return " ".join(f'{escape_attribute_name(i[0])}="{escape_attribute(i[1])}"' for i in self.attributes) |
|
|
t = Line(self.builder) |
|
|
|
|
|
self.add(t) |
|
|
|
|
|
return t |
|
|
|
|
|
|
|
|
def serialize_append_to_list(self, out, indent): |
|
|
def _line(self): |
|
|
indent_str = " " |
|
|
return Line(self) |
|
|
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): |
|
|
def print(self): |
|
|
out = [] |
|
|
out = [] |
|
@ -95,42 +85,105 @@ class Tag: |
|
|
def __enter__(self): |
|
|
def __enter__(self): |
|
|
if self.before_tag is not None: |
|
|
if self.before_tag is not None: |
|
|
raise RuntimeError("Duplicit __enter__") |
|
|
raise RuntimeError("Duplicit __enter__") |
|
|
self.before_tag = self.builder._current_tag |
|
|
self.before_tag = self.builder.current_tag |
|
|
self.builder._current_tag = self |
|
|
self.builder.current_tag = self |
|
|
return self |
|
|
return self |
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, exc_traceback): |
|
|
def __exit__(self, exc_type, exc_value, exc_traceback): |
|
|
if self.before_tag is None: |
|
|
if self.before_tag is None: |
|
|
raise RuntimeError("__exit__ before __enter__") |
|
|
raise RuntimeError("__exit__ before __enter__") |
|
|
self.builder._current_tag = self.before_tag |
|
|
self.builder.current_tag = self.before_tag |
|
|
self.before_tag = None |
|
|
self.before_tag = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(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()}>")) |
|
|
|
|
|
if indent is not None: |
|
|
|
|
|
indent += 1 |
|
|
|
|
|
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)) |
|
|
|
|
|
if indent is not None: |
|
|
|
|
|
indent -= 1 |
|
|
|
|
|
out.append(indent_str(indent, f"</{escape_tag_name(self.name)}>")) |
|
|
|
|
|
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): |
|
|
|
|
|
out.append(indent_str(indent,"")[:-1]) |
|
|
|
|
|
for i in self.content: |
|
|
|
|
|
if isinstance(i, Bucket): |
|
|
|
|
|
i.serialize_append_to_list(out, None) |
|
|
|
|
|
elif isinstance(i, Markup): |
|
|
|
|
|
out.append(i.__html__()) |
|
|
|
|
|
out.append(escape(str(i))) |
|
|
|
|
|
out.append("\n") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Builder: |
|
|
class Builder: |
|
|
_current_tag: Tag |
|
|
current_tag: Bucket |
|
|
_root_tag: Tag |
|
|
root_tag: Bucket |
|
|
def __init__(self, tag: Tag): |
|
|
def __init__(self, tag: Bucket): |
|
|
self._root_tag = tag |
|
|
self.root_tag = tag |
|
|
self._current_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 _tag(self, name: str, attributes: List[Tuple[str, str]] = []): |
|
|
def line(self): |
|
|
return self._current_tag.add_tag(name, attributes) |
|
|
return self.current_tag.line() |
|
|
|
|
|
|
|
|
|
|
|
def _line(self): |
|
|
|
|
|
return Line(self) |
|
|
|
|
|
|
|
|
def __call__(self, *arg, **kvarg): |
|
|
def __call__(self, *arg, **kvarg): |
|
|
self._current_tag(*arg, **kvarg) |
|
|
self.current_tag(*arg, **kvarg) |
|
|
return self |
|
|
return self |
|
|
|
|
|
|
|
|
def _print(self): |
|
|
def print(self): |
|
|
return self._root_tag.print() |
|
|
return self.root_tag.print() |
|
|
|
|
|
|
|
|
def _print_file(self): |
|
|
def print_file(self): |
|
|
return self._root_tag.print_file() |
|
|
return self.root_tag.print_file() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for name in tag_names: |
|
|
for name in tag_names: |
|
|
def run(name): |
|
|
def run(name): |
|
|
def l(self, **kvarg): |
|
|
def l1(self, *args, **kvarg): |
|
|
return self._tag(name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()]) |
|
|
return self.add_tag(name, [(remove_leading_underscore(k), v) for k, v in kvarg.items()])(*args) |
|
|
setattr(Builder, name, l) |
|
|
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) |
|
|
run(name) |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -149,7 +202,7 @@ def remove_leading_underscore(s): |
|
|
|
|
|
|
|
|
class WrapAfterBuilder(Builder): |
|
|
class WrapAfterBuilder(Builder): |
|
|
def __init__(self, f): |
|
|
def __init__(self, f): |
|
|
super().__init__(Tag(self, "root", [])) |
|
|
super().__init__(Bucket(self)) |
|
|
self._wrap_done = False |
|
|
self._wrap_done = False |
|
|
self._wrap_function = f |
|
|
self._wrap_function = f |
|
|
|
|
|
|
|
@ -157,18 +210,18 @@ class WrapAfterBuilder(Builder): |
|
|
if self._wrap_done: |
|
|
if self._wrap_done: |
|
|
return |
|
|
return |
|
|
self._wrap_done = True |
|
|
self._wrap_done = True |
|
|
content = self._root_tag.content |
|
|
content = self.root_tag.content |
|
|
self._root_tag = None |
|
|
self.root_tag = None |
|
|
self._current_tag = None |
|
|
self.current_tag = None |
|
|
self._root_tag = self._wrap_function(self, content, *arg, **kvarg) or self._root_tag |
|
|
self.root_tag = self._wrap_function(self, content, *arg, **kvarg) or self.root_tag |
|
|
|
|
|
|
|
|
def _print(self, *arg, **kvarg): |
|
|
def print(self, *arg, **kvarg): |
|
|
self._wrap() |
|
|
self._wrap() |
|
|
return super()._print(*arg, **kvarg) |
|
|
return super().print(*arg, **kvarg) |
|
|
|
|
|
|
|
|
def _print_file(self, *arg, **kvarg): |
|
|
def print_file(self, *arg, **kvarg): |
|
|
self._wrap() |
|
|
self._wrap() |
|
|
return super()._print_file(*arg, **kvarg) |
|
|
return super().print_file(*arg, **kvarg) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def WrapAfterBuilder_decorator(f): |
|
|
def WrapAfterBuilder_decorator(f): |
|
|