# from __future__ import annotations
# from unittest.mock import patch
from io import StringIO
from typing import Dict
from dataclasses import dataclass, field
[docs]@dataclass
class MarkupBuilder:
"""
Build basic XML-style markup from context managers.
Instances of this class are individual tags with their own attributes. They can be used in context managers\
multiple times.
Attributes
----------
element : str
The element name to use this tag as (default 'html')
text : File-like object
The cumulative text generated by this instance. It is automatically passed down to children, and is created by \
default, so it is not necessary to pass it in most of the time.
attrs : dict
The attributes to put in the opening tag
"""
element: str = "html"
text: StringIO = field(default_factory=StringIO)
attrs: Dict = field(default_factory=dict)
[docs] def self_closing(self):
"""Add self-closing tag, e.g. <img src="image.png"/>"""
self.text.write(f'<{self.element}{self._attr_string()}/>')
[docs] def __enter__(self):
"""Add opening tag, e.g. <html>"""
self.text.write(f'\n<{self.element }{self._attr_string()}>')
[docs] def __exit__(self, exc_type, exc_val, exc_tb):
"""Add closing tag, e.g. </html>"""
self.text.write(f'\n</{self.element}>')
[docs] def __add__(self, other):
"""
Add ``other`` to current text.
If ``other`` is a :ref:`MarkupBuilder` just run :ref:`MarkupBuilder.self_closing()` on it, otherwise add \
``str(other)`` to ``self.text``.
Parameters
----------
other : Union[str,MarkupBuilder]
The other object to add.
"""
if isinstance(other, MarkupBuilder):
other.self_closing()
return
self.text.write(f'\n{str(other)}')
[docs] def __getattr__(self, item):
"""Return a child of this :ref:`MarkupBuilder` with ``item`` as the :ref:`MarkupBuilder.element`"""
return MarkupBuilder(item, self.text)
[docs] def __call__(self, **kwargs):
"""Change this instance's :ref:`attrs` to ``kwargs``."""
self.attrs = kwargs
return self
[docs] def __repr__(self):
"""Get the final XML for this :ref:`MarkupBuilder`"""
return self.text.getvalue()[1:]
def _attr_string(self):
val = ' '.join(f'{key}="{value}"' for key, value in self.attrs.items())
val = ' ' + val if val else ''
return val