1"""BOOLEAN values from :rfc:`5545`."""
2
3from typing import Any, ClassVar
4from xml.etree.ElementTree import Element, SubElement
5
6from icalendar.caselessdict import CaselessDict
7from icalendar.compatibility import Self
8from icalendar.error import JCalParsingError, XCalParsingError
9from icalendar.parser import Parameters
10from icalendar.parser.xcal.base import XCalParser
11from icalendar.parser.xcal.wrapper import from_xcal_wrapper
12
13
14class vBoolean(int):
15 """An iCalendar boolean value.
16
17 Converts between iCalendar ``BOOLEAN`` value types and Python boolean values.
18 Conforming with :rfc:`5545#section-3.3.2`, iCalendar boolean values are
19 represented as ``TRUE`` or ``FALSE``. Values parsed from iCalendar text are case
20 insensitive.
21
22 Parameters:
23 params: iCalendar property parameters associated with the value.
24
25 Returns:
26 A new :class:`vBoolean` instance with the supplied property parameters.
27
28 Examples:
29 Parse, create, and use iCalendar boolean values.
30
31 .. code-block:: pycon
32
33 >>> from icalendar import vBoolean
34 >>> vBoolean.from_ical("TRUE")
35 True
36 >>> vBoolean.from_ical("fAlse")
37 False
38 >>> boolean = vBoolean(True)
39 >>> if boolean:
40 ... print("TRUE")
41 TRUE
42 >>> boolean.to_ical()
43 b'TRUE'
44
45 """
46
47 default_value: ClassVar[str] = "BOOLEAN"
48 params: Parameters
49
50 BOOL_MAP = CaselessDict({"true": True, "false": False})
51
52 def __new__(
53 cls, *args: Any, params: dict[str, Any] | None = None, **kwargs: Any
54 ) -> Self:
55 self = super().__new__(cls, *args, **kwargs)
56 self.params = Parameters(params)
57 return self
58
59 def to_ical(self) -> bytes:
60 """Converts a :class:`~icalendar.prop.boolean.vBoolean` to a BOOLEAN property type.
61
62 This class method takes a ``vBoolean``—a Python boolean value—and converts it to an iCalendar BOOLEAN property type, in compliance with :rfc:`5545#section-3.3.2`.
63
64 Returns:
65 Either "TRUE" or "FALSE" as bytes, depending on the value of the ``vBoolean``.
66 """
67 return b"TRUE" if self else b"FALSE"
68
69 @property
70 def ical_value(self) -> bool:
71 """BOOLEAN property type according to :rfc:`5545#section-3.3.2`"""
72 return bool(self)
73
74 @classmethod
75 def from_ical(cls, ical: str) -> bool:
76 try:
77 return cls.BOOL_MAP[ical]
78 except Exception as e:
79 raise ValueError(f"Expected 'TRUE' or 'FALSE'. Got {ical}") from e
80
81 @classmethod
82 def examples(cls) -> list[Self]:
83 """Examples of vBoolean."""
84 return [
85 cls(True),
86 cls(False),
87 ]
88
89 from icalendar.param import VALUE
90
91 def to_jcal(self, name: str) -> list:
92 """The jCal representation of this property according to :rfc:`7265`."""
93 return [name, self.params.to_jcal(), self.VALUE.lower(), bool(self)]
94
95 @classmethod
96 def from_jcal(cls, jcal_property: list) -> Self:
97 """Parse jCal from :rfc:`7265` to a vBoolean.
98
99 Parameters:
100 jcal_property: The jCal property to parse.
101
102 Raises:
103 ~error.JCalParsingError: If the provided jCal is invalid.
104 """
105 JCalParsingError.validate_property(jcal_property, cls)
106 JCalParsingError.validate_value_type(jcal_property[3], bool, cls, 3)
107 return cls(
108 jcal_property[3],
109 params=Parameters.from_jcal_property(jcal_property),
110 )
111
112 @classmethod
113 @from_xcal_wrapper
114 def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self:
115 """Parse xCal from :rfc:`6321`.
116
117 Parameters:
118 xml: The XML to parse or a parser.
119
120 Raises:
121 ~error.XCalParsingError: If the provided xCal is invalid.
122 """
123 element = parser.parse_tag(cls.default_value)
124 value = element.get_xsd_token().lower()
125 if value not in cls.BOOL_MAP:
126 raise XCalParsingError(
127 "Expected 'true' or 'false'",
128 element.get_xsd_token(),
129 element,
130 )
131 return cls(cls.BOOL_MAP[value], params=params)
132
133 def to_xcal(self, element: Element) -> None:
134 """The xCal representation of this property according to :rfc:`6321`."""
135 self.params.to_xcal(element)
136 element = SubElement(element, self.default_value.lower())
137 element.text = "true" if self else "false"
138
139
140__all__ = ["vBoolean"]