WireViz/src/wireviz/wv_table_util.py
2023-09-12 19:37:08 +02:00

112 lines
2.9 KiB
Python

# -*- coding: utf-8 -*-
from collections.abc import Iterable
from dataclasses import dataclass, field
from typing import Dict, List, Optional
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: str
attribs: Attribs = field(default_factory=Attribs)
flat: bool = False
empty_is_none: bool = False
def __post_init__(self):
if self.attribs is None:
self.attribs = Attribs({})
elif isinstance(self.attribs, Dict):
self.attribs = Attribs(self.attribs)
elif not isinstance(self.attribs, Attribs):
raise Exception(
"Tag.attribs must be of type None, Dict, or Attribs, "
f"but type {type(self.attribs).__name__} was given instead:\n"
f"{self.attribs}"
)
@property
def tagname(self):
return type(self).__name__.lower()
@property
def auto_flat(self):
if self.flat: # force flat
return True
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
def indent_lines(self, lines):
if self.auto_flat:
return lines
else:
indenter = " " * indent_count
return "\n".join(f"{indenter}{line}" for line in lines.split("\n"))
def get_contents(self):
separator = "" if self.auto_flat else "\n"
if _is_iterable_not_str(self.contents):
return separator.join(
[self.indent_lines(str(c)) 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))
def __repr__(self):
separator = "" if self.auto_flat else "\n"
if self.contents is None and self.empty_is_none:
return ""
else:
html = [
f"<{self.tagname}{str(self.attribs)}>",
f"{self.get_contents()}",
f"</{self.tagname}>",
]
return separator.join(html)
@dataclass
class TagSingleton(Tag):
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 Td(Tag):
pass
class Tr(Tag):
pass
class Table(Tag):
pass