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