WireViz/src/wireviz/wv_html.py

126 lines
3.1 KiB
Python

# -*- coding: utf-8 -*-
from collections.abc import Iterable
from dataclasses import dataclass, field
from typing import Dict
indent_count = 1
class Attribs(Dict):
def __repr__(self):
if len(self) == 0:
return ""
html = []
for k, v in self.items():
if v is not None:
html.append(f' {k}="{v}"')
# else:
# html.append(f" {k}")
return "".join(html)
@dataclass
class Tag:
contents = None
attribs: Attribs = field(default_factory=Attribs)
flat: bool = None
delete_if_empty: bool = False
def __init__(self, contents, flat=None, delete_if_empty=False, **kwargs):
self.contents = contents
self.flat = flat
self.delete_if_empty = delete_if_empty
self.attribs = Attribs({**kwargs})
def update_attribs(self, **kwargs):
for k, v in kwargs.items():
self.attribs[k] = v
@property
def tagname(self):
return type(self).__name__.lower()
@property
def auto_flat(self):
if self.flat is not None: # user specified
return self.flat
if not _is_iterable_not_str(self.contents): # catch str, int, float, ...
if not isinstance(self.contents, Tag): # avoid recursion
return not "\n" in str(self.contents) # flatten if single line
@property
def is_empty(self):
return self.get_contents(force_flat=True) == ""
def indent_lines(self, lines, force_flat=False):
if self.auto_flat or force_flat:
return lines
else:
indenter = " " * indent_count
return "\n".join(f"{indenter}{line}" for line in lines.split("\n"))
def get_contents(self, force_flat=False):
separator = "" if self.auto_flat or force_flat else "\n"
if _is_iterable_not_str(self.contents):
return separator.join(
[
self.indent_lines(str(c), force_flat)
for c in self.contents
if c is not None
]
)
elif self.contents is None:
return ""
else: # str, int, float, etc.
return self.indent_lines(str(self.contents), force_flat)
def __repr__(self):
separator = "" if self.auto_flat else "\n"
if self.delete_if_empty and self.is_empty:
return ""
else:
html = [
f"<{self.tagname}{str(self.attribs)}>",
f"{self.get_contents()}",
f"</{self.tagname}>",
]
html_joined = separator.join(html)
return html_joined
@dataclass
class TagSingleton(Tag):
def __init__(self, **kwargs):
self.attribs = Attribs({**kwargs})
def __repr__(self):
return f"<{self.tagname}{str(self.attribs)} />"
def _is_iterable_not_str(inp):
# str is iterable, but should be treated as not iterable
return isinstance(inp, Iterable) and not isinstance(inp, str)
@dataclass
class Br(TagSingleton):
pass
class Img(TagSingleton):
pass
class Td(Tag):
pass
class Tr(Tag):
pass
class Table(Tag):
pass