class html_attribute:
    """Add an attribute to an HTML element.

        >>> from htmlgen import Element
        >>> class MyElement(Element):
        ...     url = html_attribute("data-url")
        >>> element = MyElement("div")
        >>> element.url
        >>> str(element)
        '<div></div>'
        >>> element.url = "http://www.example.com/"
        >>> str(element)
        '<div data-url="http://www.example.com/"></div>'

    If the optional default argument is given, the attribute will not be
    included if the value matches it.

        >>> class MyElement(Element):
        ...     value = html_attribute("data-value", default="hello")
        >>> element = MyElement("div")
        >>> element.value
        'hello'
        >>> str(element)
        '<div></div>'

    """

    def __init__(self, attribute_name, default=None):
        self._attribute_name = attribute_name
        self._default = default

    def __get__(self, obj, _=None):
        return obj.get_attribute(self._attribute_name, default=self._default)

    def __set__(self, obj, value):
        if value is None or value == self._default:
            obj.remove_attribute(self._attribute_name)
        else:
            obj.set_attribute(self._attribute_name, value) class boolean_html_attribute:
    """Add a boolean attribute to an HTML element.

        >>> from htmlgen import Element
        >>> class MyElement(Element):
        ...     doit = boolean_html_attribute("data-doit")
        >>> element = MyElement("div")
        >>> element.doit
        False
        >>> str(element)
        '<div></div>'
        >>> element.doit = True
        >>> str(element)
        '<div data-doit="data-doit"></div>'

    """

    def __init__(self, attribute_name):
        self._attribute_name = attribute_name

    def __get__(self, obj, _=None):
        return obj.get_attribute(self._attribute_name) == self._attribute_name

    def __set__(self, obj, value):
        if value:
            obj.set_attribute(self._attribute_name, self._attribute_name)
        else:
            obj.remove_attribute(self._attribute_name) class int_html_attribute:
    """Add an attribute to an HTML element that accepts only integers.

        >>> from htmlgen import Element
        >>> class MyElement(Element):
        ...     value = int_html_attribute("data-value")
        >>> element = MyElement("div")
        >>> element.value
        >>> str(element)
        '<div></div>'
        >>> element.value = 42
        >>> str(element)
        '<div data-value="42"></div>'

    If the optional default argument is given, the attribute will not be
    included if the value matches it.

        >>> class MyElement(Element):
        ...     value = int_html_attribute("data-value", default=0)
        >>> element = MyElement("div")
        >>> element.value
        0
        >>> str(element)
        '<div></div>'

    """

    def __init__(self, attribute_name, default=None):
        self._attribute_name = attribute_name
        self._default = default

    def __get__(self, obj, _=None):
        value = obj.get_attribute(self._attribute_name, default=self._default)
        if value is None:
            return None
        return int(value)

    def __set__(self, obj, value):
        if value is None or value == self._default:
            obj.remove_attribute(self._attribute_name)
        else:
            obj.set_attribute(self._attribute_name, str(value)) class float_html_attribute:
    """Add an attribute to an HTML element that accepts only numbers.

        >>> from htmlgen import Element
        >>> class MyElement(Element):
        ...     value = float_html_attribute("data-value")
        >>> element = MyElement("div")
        >>> element.value
        >>> str(element)
        '<div></div>'
        >>> element.value = 4.2
        >>> str(element)
        '<div data-value="4.2"></div>'

    If the optional default argument is given, the attribute will not be
    included if the value matches it.

        >>> class MyElement(Element):
        ...     value = float_html_attribute("data-value", default=0.4)
        >>> element = MyElement("div")
        >>> element.value
        0.4
        >>> str(element)
        '<div></div>'

    """

    def __init__(self, attribute_name, default=None):
        self._attribute_name = attribute_name
        self._default = default

    def __get__(self, obj, _=None):
        value = obj.get_attribute(self._attribute_name, default=self._default)
        if value is None:
            return None
        return float(value)

    def __set__(self, obj, value):
        if value is None or value == self._default:
            obj.remove_attribute(self._attribute_name)
        else:
            obj.set_attribute(self._attribute_name, str(value)) class time_html_attribute:
    """Add an attribute to an HTML element that accepts only time values.

    >>> from htmlgen import Element
    >>> class MyElement(Element):
    ...     time = time_html_attribute("data-time")
    >>> element = MyElement("div")
    >>> element.time
    >>> str(element)
    '<div></div>'
    >>> element.time = datetime.time(8, 15)
    >>> str(element)
    '<div data-time="08:15:00"></div>'

    If the optional default argument is given, the attribute will not be
    included if the value matches it.

    >>> class MyElement(Element):
    ...     value = time_html_attribute(
    ...         "data-value", default=datetime.time(8, 0))
    >>> element = MyElement("div")
    >>> element.value
    datetime.time(8, 0)
    >>> str(element)
    '<div></div>'

    """

    def __init__(self, attribute_name, default=None):
        self._attribute_name = attribute_name
        self._default = default

    def __get__(self, obj, _=None):
        value = obj.get_attribute(self._attribute_name)
        if value is None:
            return self._default
        return parse_rfc3339_partial_time(value)

    def __set__(self, obj, value):
        if value is None or value == self._default:
            obj.remove_attribute(self._attribute_name)
        else:
            obj.set_attribute(self._attribute_name, str(value)) class list_html_attribute:
    """Add an attribute to an HTML element that accepts a list of strings.

    >>> from htmlgen import Element
    >>> class MyElement(Element):
    ...     list = list_html_attribute("data-list")
    >>> element = MyElement("div")
    >>> element.list
    []
    >>> str(element)
    '<div></div>'
    >>> element.list = ["foo", "bar"]
    >>> str(element)
    '<div data-list="foo,bar"></div>'

    """

    def __init__(self, attribute_name):
        self._attribute_name = attribute_name

    def __get__(self, obj, _=None):
        value = obj.get_attribute(self._attribute_name)
        return value.split(",") if value else []

    def __set__(self, obj, value):
        if value:
            obj.set_attribute(self._attribute_name, ",".join(value))
        else:
            obj.remove_attribute(self._attribute_name) class data_attribute(html_attribute):
    def __init__(self, data_name, default=None):
        attribute_name = "data-" + data_name
        super(data_attribute, self).__init__(attribute_name, default) class css_class_attribute:
    """Add a boolean attribute to an HTML element that add a CSS class.

    >>> from htmlgen import Element
    >>> class MyElement(Element):
    ...     test = css_class_attribute("my-class")
    >>> element = MyElement("div")
    >>> element.add_css_classes("other-class")
    >>> element.test
    False
    >>> str(element)
    '<div class="other-class"></div>'
    >>> element.test = True
    >>> str(element)
    '<div class="my-class other-class"></div>'

    """

    def __init__(self, css_class):
        self._css_class = css_class

    def __get__(self, obj, _=None):
        return obj.has_css_class(self._css_class)

    def __set__(self, obj, value):
        if value:
            obj.add_css_classes(self._css_class)
        else:
            obj.remove_css_classes(self._css_class) class enum_attribute:
    """Add an attribute to an HTML element that only allows limited values.

        >>> from enum import Enum
        >>> from htmlgen import Element
        >>> class MyEnum(Enum):
        ...     FOO = "foo"
        ...     BAR = "bar"
        >>> class MyElement(Element):
        ...     value = enum_attribute("value", MyEnum)
        >>> element = MyElement("div")
        >>> element.value
        >>> str(element)
        '<div></div>'
        >>> element.value = MyEnum.FOO
        >>> str(element)
        '<div value="foo"></div>'

    If the optional default argument is given, the attribute will not be
    included if the value matches it.

        >>> class MyElement(Element):
        ...     value = enum_attribute("value", MyEnum, default=MyEnum.FOO)
        >>> element = MyElement("div")
        >>> element.value
        MyEnum.FOO
        >>> str(element)
        '<div></div>'
    """

    def __init__(self, attribute_name, enum, default=None):
        if not issubclass(enum, Enum):
            raise TypeError("enum must be an Enum class")
        self._attribute_name = attribute_name
        self._enum = enum
        self._default = default

    def __get__(self, obj, _=None):
        value = obj.get_attribute(self._attribute_name, None)
        if value is None:
            return self._default
        return self._enum(value)

    def __set__(self, obj, value):
        if value is None:
            obj.remove_attribute(self._attribute_name)
        elif not isinstance(value, self._enum):
            raise TypeError("value must be an {}".format(self._enum))
        else:
            obj.set_attribute(self._attribute_name, value.value) class Element(NonVoidElement):

    """Base generator for HTML elements with children.

    This includes most elements, like <body>, <div>, <span>, <strong>, etc.

        >>> link = Element("a")
        >>> link.set_attribute("href", "http://www.example.com/")
        >>> link.append("Test Link")
        >>> str(link)
        '<a href="http://www.example.com/">Test Link</a>'

    append() and extend() escape reserved HTML characters in string, but
    not in sub-generators:

        >>> span = Element("span")
        >>> span.add_css_classes("my-css")
        >>> span.set_style("color", "red")
        >>> span.append("Test & Test")
        >>> str(span)
        '<span class="my-css" style="color: red">Test &amp; Test</span>'

    append_raw() and extend_raw() do not do any escaping. Do not use these
    methods with strings from untrusted sources!

        >>> span = Element("span")
        >>> span.append_raw("Test<br/>Test")
        >>> str(span)
        '<span>Test<br/>Test</span>'

    Instead of appending children to elements, the generate_children()
    method can be overwritten. This is generally preferred for element
    sub-classes that are not meant to be extended by users, since it avoids
    constructing the HTML tree in memory.

        >>> class MyElement(Element):
        ...     def generate_children(self):
        ...         yield "Hello, "
        ...         child = Element("strong")
        ...         child.append("World!")
        ...         yield child
        >>> str(MyElement("div"))
        '<div>Hello, <strong>World!</strong></div>'

    """

    def __init__(self, element_name):
        super().__init__(element_name)
        self.children = HTMLChildGenerator()

    def __bool__(self):
        return True

    def __getattr__(self, item):
        return getattr(self.children, item)

    def __len__(self):
        """Return the number of children.

        A sub-generator is counted as one child.

        """
        return len(self.children)

    def __nonzero__(self):
        return True

    def generate_children(self):
        """Return an iterator over the children of this element.

        This method can be overridden by sub-classes.

        """
        return self.children
import datetime
from enum import Enum
from unittest import TestCase
from asserts import assert_true, assert_false, assert_is_none, assert_equal, \
    assert_raises
from htmlgen.attribute import (
    html_attribute,
    boolean_html_attribute,
    int_html_attribute,
    float_html_attribute,
    time_html_attribute,
    list_html_attribute,
    data_attribute,
    css_class_attribute,
    enum_attribute,
)
from htmlgen.element import Element




class HTMLAttributeTest(TestCase):
    def test_regular(self):
        class MyElement(Element):
            attr = html_attribute("data-attr")

        element = MyElement("div")
        assert_is_none(element.attr)
        assert_equal("<div></div>", str(element))
        element.attr = "Foo"
        assert_equal("Foo", element.attr)
        assert_equal('<div data-attr="Foo"></div>', str(element))
        element.attr = None
        assert_is_none(element.attr)
        assert_equal("<div></div>", str(element))

    def test_regular_with_default(self):
        class MyElement(Element):
            attr = html_attribute("data-attr", default="Bar")

        element = MyElement("div")
        assert_equal("Bar", element.attr)
        assert_equal("<div></div>", str(element))
        element.attr = "Foo"
        assert_equal("Foo", element.attr)
        assert_equal('<div data-attr="Foo"></div>', str(element))
        element.attr = "Bar"
        assert_equal("Bar", element.attr)
        assert_equal("<div></div>", str(element))
        element.attr = None
        assert_equal("Bar", element.attr)
        assert_equal("<div></div>", str(element))

    def test_boolean(self):
        class MyElement(Element):
            attr = boolean_html_attribute("data-attr")

        element = MyElement("div")
        assert_false(element.attr)
        assert_equal("<div></div>", str(element))
        element.attr = True
        assert_true(element.attr)
        assert_equal('<div data-attr="data-attr"></div>', str(element))
        element.attr = False
        assert_false(element.attr)
        assert_equal("<div></div>", str(element))

    def test_integer(self):
        class MyElement(Element):
            attr = int_html_attribute("data-attr")

        element = MyElement("div")
        assert_is_none(element.attr)
        assert_equal("<div></div>", str(element))
        element.attr = 42
        assert_equal(42, element.attr)
        assert_equal('<div data-attr="42"></div>', str(element))
        element.attr = None
        assert_is_none(element.attr)
        assert_equal("<div></div>", str(element))

    def test_integer_with_default(self):
        class MyElement(Element):
            attr = int_html_attribute("data-attr", default=42)

        element = MyElement("div")
        assert_equal(42, element.attr)
        assert_equal("<div></div>", str(element))
        element.attr = 4711
        assert_equal(4711, element.attr)
        assert_equal('<div data-attr="4711"></div>', str(element))
        element.attr = 42
        assert_equal(42, element.attr)
        assert_equal("<div></div>", str(element))
        element.attr = None
        assert_equal(42, element.attr)
        assert_equal("<div></div>", str(element))

    def test_float(self):
        class MyElement(Element):
            attr = float_html_attribute("data-attr")

        element = MyElement("div")
        assert_is_none(element.attr)
        assert_equal("<div></div>", str(element))
        element.attr = 4.2
        assert_equal(4.2, element.attr)
        assert_equal('<div data-attr="4.2"></div>', str(element))
        element.attr = None
        assert_is_none(element.attr)
        assert_equal("<div></div>", str(element))

    def test_float_with_default(self):
        class MyElement(Element):

            attr = float_html_attribute("data-attr", default=4.2)