Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/items.py: 69%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import abc
4import copy
5import dataclasses
6import math
7import re
8import string
9import sys
11from datetime import date
12from datetime import datetime
13from datetime import time
14from datetime import tzinfo
15from enum import Enum
16from typing import TYPE_CHECKING
17from typing import Any
18from typing import Callable
19from typing import Collection
20from typing import Iterable
21from typing import Iterator
22from typing import Sequence
23from typing import TypeVar
24from typing import cast
25from typing import overload
27from tomlkit._compat import PY38
28from tomlkit._compat import decode
29from tomlkit._types import _CustomDict
30from tomlkit._types import _CustomFloat
31from tomlkit._types import _CustomInt
32from tomlkit._types import _CustomList
33from tomlkit._types import wrap_method
34from tomlkit._utils import CONTROL_CHARS
35from tomlkit._utils import escape_string
36from tomlkit.exceptions import ConvertError
37from tomlkit.exceptions import InvalidStringError
40if TYPE_CHECKING:
41 from tomlkit import container
44ItemT = TypeVar("ItemT", bound="Item")
45Encoder = Callable[[Any], "Item"]
46CUSTOM_ENCODERS: list[Encoder] = []
47AT = TypeVar("AT", bound="AbstractTable")
50@overload
51def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ...
54@overload
55def item(value: int, _parent: Item | None = ..., _sort_keys: bool = ...) -> Integer: ...
58@overload
59def item(value: float, _parent: Item | None = ..., _sort_keys: bool = ...) -> Float: ...
62@overload
63def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> String: ...
66@overload
67def item(
68 value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...
69) -> DateTime: ...
72@overload
73def item(value: date, _parent: Item | None = ..., _sort_keys: bool = ...) -> Date: ...
76@overload
77def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Time: ...
80@overload
81def item(
82 value: Sequence[dict], _parent: Item | None = ..., _sort_keys: bool = ...
83) -> AoT: ...
86@overload
87def item(
88 value: Sequence, _parent: Item | None = ..., _sort_keys: bool = ...
89) -> Array: ...
92@overload
93def item(value: dict, _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ...
96@overload
97def item(value: dict, _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ...
100@overload
101def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ...
104def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item:
105 """Create a TOML item from a Python object.
107 :Example:
109 >>> item(42)
110 42
111 >>> item([1, 2, 3])
112 [1, 2, 3]
113 >>> item({'a': 1, 'b': 2})
114 a = 1
115 b = 2
116 """
118 from tomlkit.container import Container
120 if isinstance(value, Item):
121 return value
123 if isinstance(value, bool):
124 return Bool(value, Trivia())
125 elif isinstance(value, int):
126 return Integer(value, Trivia(), str(value))
127 elif isinstance(value, float):
128 return Float(value, Trivia(), str(value))
129 elif isinstance(value, dict):
130 table_constructor = (
131 InlineTable if isinstance(_parent, (Array, InlineTable)) else Table
132 )
133 val = table_constructor(Container(), Trivia(), False)
134 for k, v in sorted(
135 value.items(),
136 key=lambda i: (isinstance(i[1], dict), i[0]) if _sort_keys else 1,
137 ):
138 val[k] = item(v, _parent=val, _sort_keys=_sort_keys)
140 return val
141 elif isinstance(value, (list, tuple)):
142 if (
143 value
144 and all(isinstance(v, dict) for v in value)
145 and (_parent is None or isinstance(_parent, Table))
146 ):
147 a = AoT([])
148 table_constructor = Table
149 else:
150 a = Array([], Trivia())
151 table_constructor = InlineTable
153 for v in value:
154 if isinstance(v, dict):
155 table = table_constructor(Container(), Trivia(), True)
157 for k, _v in sorted(
158 v.items(),
159 key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
160 ):
161 i = item(_v, _parent=table, _sort_keys=_sort_keys)
162 if isinstance(table, InlineTable):
163 i.trivia.trail = ""
165 table[k] = i
167 v = table
169 a.append(v)
171 return a
172 elif isinstance(value, str):
173 return String.from_raw(value)
174 elif isinstance(value, datetime):
175 return DateTime(
176 value.year,
177 value.month,
178 value.day,
179 value.hour,
180 value.minute,
181 value.second,
182 value.microsecond,
183 value.tzinfo,
184 Trivia(),
185 value.isoformat().replace("+00:00", "Z"),
186 )
187 elif isinstance(value, date):
188 return Date(value.year, value.month, value.day, Trivia(), value.isoformat())
189 elif isinstance(value, time):
190 return Time(
191 value.hour,
192 value.minute,
193 value.second,
194 value.microsecond,
195 value.tzinfo,
196 Trivia(),
197 value.isoformat(),
198 )
199 else:
200 for encoder in CUSTOM_ENCODERS:
201 try:
202 rv = encoder(value)
203 except ConvertError:
204 pass
205 else:
206 if not isinstance(rv, Item):
207 raise ConvertError(
208 f"Custom encoder is expected to return an instance of Item, got {type(rv)}"
209 )
210 return rv
212 raise ConvertError(f"Unable to convert an object of {type(value)} to a TOML item")
215class StringType(Enum):
216 # Single Line Basic
217 SLB = '"'
218 # Multi Line Basic
219 MLB = '"""'
220 # Single Line Literal
221 SLL = "'"
222 # Multi Line Literal
223 MLL = "'''"
225 @classmethod
226 def select(cls, literal=False, multiline=False) -> StringType:
227 return {
228 (False, False): cls.SLB,
229 (False, True): cls.MLB,
230 (True, False): cls.SLL,
231 (True, True): cls.MLL,
232 }[(literal, multiline)]
234 @property
235 def escaped_sequences(self) -> Collection[str]:
236 # https://toml.io/en/v1.0.0#string
237 escaped_in_basic = CONTROL_CHARS | {"\\"}
238 allowed_in_multiline = {"\n", "\r"}
239 return {
240 StringType.SLB: escaped_in_basic | {'"'},
241 StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline,
242 StringType.SLL: (),
243 StringType.MLL: (),
244 }[self]
246 @property
247 def invalid_sequences(self) -> Collection[str]:
248 # https://toml.io/en/v1.0.0#string
249 forbidden_in_literal = CONTROL_CHARS - {"\t"}
250 allowed_in_multiline = {"\n", "\r"}
251 return {
252 StringType.SLB: (),
253 StringType.MLB: (),
254 StringType.SLL: forbidden_in_literal | {"'"},
255 StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline,
256 }[self]
258 @property
259 def unit(self) -> str:
260 return self.value[0]
262 def is_basic(self) -> bool:
263 return self in {StringType.SLB, StringType.MLB}
265 def is_literal(self) -> bool:
266 return self in {StringType.SLL, StringType.MLL}
268 def is_singleline(self) -> bool:
269 return self in {StringType.SLB, StringType.SLL}
271 def is_multiline(self) -> bool:
272 return self in {StringType.MLB, StringType.MLL}
274 def toggle(self) -> StringType:
275 return {
276 StringType.SLB: StringType.MLB,
277 StringType.MLB: StringType.SLB,
278 StringType.SLL: StringType.MLL,
279 StringType.MLL: StringType.SLL,
280 }[self]
283class BoolType(Enum):
284 TRUE = "true"
285 FALSE = "false"
287 def __bool__(self):
288 return {BoolType.TRUE: True, BoolType.FALSE: False}[self]
290 def __iter__(self):
291 return iter(self.value)
293 def __len__(self):
294 return len(self.value)
297@dataclasses.dataclass
298class Trivia:
299 """
300 Trivia information (aka metadata).
301 """
303 # Whitespace before a value.
304 indent: str = ""
305 # Whitespace after a value, but before a comment.
306 comment_ws: str = ""
307 # Comment, starting with # character, or empty string if no comment.
308 comment: str = ""
309 # Trailing newline.
310 trail: str = "\n"
312 def copy(self) -> Trivia:
313 return dataclasses.replace(self)
316class KeyType(Enum):
317 """
318 The type of a Key.
320 Keys can be bare (unquoted), or quoted using basic ("), or literal (')
321 quotes following the same escaping rules as single-line StringType.
322 """
324 Bare = ""
325 Basic = '"'
326 Literal = "'"
329class Key(abc.ABC):
330 """Base class for a key"""
332 sep: str
333 _original: str
334 _keys: list[SingleKey]
335 _dotted: bool
336 key: str
338 @abc.abstractmethod
339 def __hash__(self) -> int:
340 pass
342 @abc.abstractmethod
343 def __eq__(self, __o: object) -> bool:
344 pass
346 def is_dotted(self) -> bool:
347 """If the key is followed by other keys"""
348 return self._dotted
350 def __iter__(self) -> Iterator[SingleKey]:
351 return iter(self._keys)
353 def concat(self, other: Key) -> DottedKey:
354 """Concatenate keys into a dotted key"""
355 keys = self._keys + other._keys
356 return DottedKey(keys, sep=self.sep)
358 def is_multi(self) -> bool:
359 """Check if the key contains multiple keys"""
360 return len(self._keys) > 1
362 def as_string(self) -> str:
363 """The TOML representation"""
364 return self._original
366 def __str__(self) -> str:
367 return self.as_string()
369 def __repr__(self) -> str:
370 return f"<Key {self.as_string()}>"
373class SingleKey(Key):
374 """A single key"""
376 def __init__(
377 self,
378 k: str,
379 t: KeyType | None = None,
380 sep: str | None = None,
381 original: str | None = None,
382 ) -> None:
383 if not isinstance(k, str):
384 raise TypeError("Keys must be strings")
386 if t is None:
387 if not k or any(
388 c not in string.ascii_letters + string.digits + "-" + "_" for c in k
389 ):
390 t = KeyType.Basic
391 else:
392 t = KeyType.Bare
394 self.t = t
395 if sep is None:
396 sep = " = "
398 self.sep = sep
399 self.key = k
400 if original is None:
401 key_str = escape_string(k) if t == KeyType.Basic else k
402 original = f"{t.value}{key_str}{t.value}"
404 self._original = original
405 self._keys = [self]
406 self._dotted = False
408 @property
409 def delimiter(self) -> str:
410 """The delimiter: double quote/single quote/none"""
411 return self.t.value
413 def is_bare(self) -> bool:
414 """Check if the key is bare"""
415 return self.t == KeyType.Bare
417 def __hash__(self) -> int:
418 return hash(self.key)
420 def __eq__(self, other: Any) -> bool:
421 if isinstance(other, Key):
422 return isinstance(other, SingleKey) and self.key == other.key
424 return self.key == other
427class DottedKey(Key):
428 def __init__(
429 self,
430 keys: Iterable[SingleKey],
431 sep: str | None = None,
432 original: str | None = None,
433 ) -> None:
434 self._keys = list(keys)
435 if original is None:
436 original = ".".join(k.as_string() for k in self._keys)
438 self.sep = " = " if sep is None else sep
439 self._original = original
440 self._dotted = False
441 self.key = ".".join(k.key for k in self._keys)
443 def __hash__(self) -> int:
444 return hash(tuple(self._keys))
446 def __eq__(self, __o: object) -> bool:
447 return isinstance(__o, DottedKey) and self._keys == __o._keys
450class Item:
451 """
452 An item within a TOML document.
453 """
455 def __init__(self, trivia: Trivia) -> None:
456 self._trivia = trivia
458 @property
459 def trivia(self) -> Trivia:
460 """The trivia element associated with this item"""
461 return self._trivia
463 @property
464 def discriminant(self) -> int:
465 raise NotImplementedError()
467 def as_string(self) -> str:
468 """The TOML representation"""
469 raise NotImplementedError()
471 @property
472 def value(self) -> Any:
473 return self
475 def unwrap(self) -> Any:
476 """Returns as pure python object (ppo)"""
477 raise NotImplementedError()
479 # Helpers
481 def comment(self, comment: str) -> Item:
482 """Attach a comment to this item"""
483 if not comment.strip().startswith("#"):
484 comment = "# " + comment
486 self._trivia.comment_ws = " "
487 self._trivia.comment = comment
489 return self
491 def indent(self, indent: int) -> Item:
492 """Indent this item with given number of spaces"""
493 if self._trivia.indent.startswith("\n"):
494 self._trivia.indent = "\n" + " " * indent
495 else:
496 self._trivia.indent = " " * indent
498 return self
500 def is_boolean(self) -> bool:
501 return isinstance(self, Bool)
503 def is_table(self) -> bool:
504 return isinstance(self, Table)
506 def is_inline_table(self) -> bool:
507 return isinstance(self, InlineTable)
509 def is_aot(self) -> bool:
510 return isinstance(self, AoT)
512 def _getstate(self, protocol=3):
513 return (self._trivia,)
515 def __reduce__(self):
516 return self.__reduce_ex__(2)
518 def __reduce_ex__(self, protocol):
519 return self.__class__, self._getstate(protocol)
522class Whitespace(Item):
523 """
524 A whitespace literal.
525 """
527 def __init__(self, s: str, fixed: bool = False) -> None:
528 self._s = s
529 self._fixed = fixed
531 @property
532 def s(self) -> str:
533 return self._s
535 @property
536 def value(self) -> str:
537 """The wrapped string of the whitespace"""
538 return self._s
540 @property
541 def trivia(self) -> Trivia:
542 raise RuntimeError("Called trivia on a Whitespace variant.")
544 @property
545 def discriminant(self) -> int:
546 return 0
548 def is_fixed(self) -> bool:
549 """If the whitespace is fixed, it can't be merged or discarded from the output."""
550 return self._fixed
552 def as_string(self) -> str:
553 return self._s
555 def __repr__(self) -> str:
556 return f"<{self.__class__.__name__} {self._s!r}>"
558 def _getstate(self, protocol=3):
559 return self._s, self._fixed
562class Comment(Item):
563 """
564 A comment literal.
565 """
567 @property
568 def discriminant(self) -> int:
569 return 1
571 def as_string(self) -> str:
572 return (
573 f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
574 )
576 def __str__(self) -> str:
577 return f"{self._trivia.indent}{decode(self._trivia.comment)}"
580class Integer(Item, _CustomInt):
581 """
582 An integer literal.
583 """
585 def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
586 return int.__new__(cls, value)
588 def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
589 super().__init__(trivia)
590 self._original = value
591 self._raw = raw
592 self._sign = False
594 if re.match(r"^[+\-]\d+$", raw):
595 self._sign = True
597 def unwrap(self) -> int:
598 return self._original
600 __int__ = unwrap
602 def __hash__(self) -> int:
603 return hash(self.unwrap())
605 @property
606 def discriminant(self) -> int:
607 return 2
609 @property
610 def value(self) -> int:
611 """The wrapped integer value"""
612 return self
614 def as_string(self) -> str:
615 return self._raw
617 def _new(self, result):
618 raw = str(result)
619 if self._sign and result >= 0:
620 raw = f"+{raw}"
622 return Integer(result, self._trivia, raw)
624 def _getstate(self, protocol=3):
625 return int(self), self._trivia, self._raw
627 # int methods
628 __abs__ = wrap_method(int.__abs__)
629 __add__ = wrap_method(int.__add__)
630 __and__ = wrap_method(int.__and__)
631 __ceil__ = wrap_method(int.__ceil__)
632 __eq__ = int.__eq__
633 __floor__ = wrap_method(int.__floor__)
634 __floordiv__ = wrap_method(int.__floordiv__)
635 __invert__ = wrap_method(int.__invert__)
636 __le__ = int.__le__
637 __lshift__ = wrap_method(int.__lshift__)
638 __lt__ = int.__lt__
639 __mod__ = wrap_method(int.__mod__)
640 __mul__ = wrap_method(int.__mul__)
641 __neg__ = wrap_method(int.__neg__)
642 __or__ = wrap_method(int.__or__)
643 __pos__ = wrap_method(int.__pos__)
644 __pow__ = wrap_method(int.__pow__)
645 __radd__ = wrap_method(int.__radd__)
646 __rand__ = wrap_method(int.__rand__)
647 __rfloordiv__ = wrap_method(int.__rfloordiv__)
648 __rlshift__ = wrap_method(int.__rlshift__)
649 __rmod__ = wrap_method(int.__rmod__)
650 __rmul__ = wrap_method(int.__rmul__)
651 __ror__ = wrap_method(int.__ror__)
652 __round__ = wrap_method(int.__round__)
653 __rpow__ = wrap_method(int.__rpow__)
654 __rrshift__ = wrap_method(int.__rrshift__)
655 __rshift__ = wrap_method(int.__rshift__)
656 __rxor__ = wrap_method(int.__rxor__)
657 __trunc__ = wrap_method(int.__trunc__)
658 __xor__ = wrap_method(int.__xor__)
660 def __rtruediv__(self, other):
661 result = int.__rtruediv__(self, other)
662 if result is NotImplemented:
663 return result
664 return Float._new(self, result)
666 def __truediv__(self, other):
667 result = int.__truediv__(self, other)
668 if result is NotImplemented:
669 return result
670 return Float._new(self, result)
673class Float(Item, _CustomFloat):
674 """
675 A float literal.
676 """
678 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
679 return float.__new__(cls, value)
681 def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
682 super().__init__(trivia)
683 self._original = value
684 self._raw = raw
685 self._sign = False
687 if re.match(r"^[+\-].+$", raw):
688 self._sign = True
690 def unwrap(self) -> float:
691 return self._original
693 __float__ = unwrap
695 def __hash__(self) -> int:
696 return hash(self.unwrap())
698 @property
699 def discriminant(self) -> int:
700 return 3
702 @property
703 def value(self) -> float:
704 """The wrapped float value"""
705 return self
707 def as_string(self) -> str:
708 return self._raw
710 def _new(self, result):
711 raw = str(result)
713 if self._sign and result >= 0:
714 raw = f"+{raw}"
716 return Float(result, self._trivia, raw)
718 def _getstate(self, protocol=3):
719 return float(self), self._trivia, self._raw
721 # float methods
722 __abs__ = wrap_method(float.__abs__)
723 __add__ = wrap_method(float.__add__)
724 __eq__ = float.__eq__
725 __floordiv__ = wrap_method(float.__floordiv__)
726 __le__ = float.__le__
727 __lt__ = float.__lt__
728 __mod__ = wrap_method(float.__mod__)
729 __mul__ = wrap_method(float.__mul__)
730 __neg__ = wrap_method(float.__neg__)
731 __pos__ = wrap_method(float.__pos__)
732 __pow__ = wrap_method(float.__pow__)
733 __radd__ = wrap_method(float.__radd__)
734 __rfloordiv__ = wrap_method(float.__rfloordiv__)
735 __rmod__ = wrap_method(float.__rmod__)
736 __rmul__ = wrap_method(float.__rmul__)
737 __round__ = wrap_method(float.__round__)
738 __rpow__ = wrap_method(float.__rpow__)
739 __rtruediv__ = wrap_method(float.__rtruediv__)
740 __truediv__ = wrap_method(float.__truediv__)
741 __trunc__ = float.__trunc__
743 if sys.version_info >= (3, 9):
744 __ceil__ = float.__ceil__
745 __floor__ = float.__floor__
746 else:
747 __ceil__ = math.ceil
748 __floor__ = math.floor
751class Bool(Item):
752 """
753 A boolean literal.
754 """
756 def __init__(self, t: int, trivia: Trivia) -> None:
757 super().__init__(trivia)
759 self._value = bool(t)
761 def unwrap(self) -> bool:
762 return bool(self)
764 @property
765 def discriminant(self) -> int:
766 return 4
768 @property
769 def value(self) -> bool:
770 """The wrapped boolean value"""
771 return self._value
773 def as_string(self) -> str:
774 return str(self._value).lower()
776 def _getstate(self, protocol=3):
777 return self._value, self._trivia
779 def __bool__(self):
780 return self._value
782 __nonzero__ = __bool__
784 def __eq__(self, other):
785 if not isinstance(other, bool):
786 return NotImplemented
788 return other == self._value
790 def __hash__(self):
791 return hash(self._value)
793 def __repr__(self):
794 return repr(self._value)
797class DateTime(Item, datetime):
798 """
799 A datetime literal.
800 """
802 def __new__(
803 cls,
804 year: int,
805 month: int,
806 day: int,
807 hour: int,
808 minute: int,
809 second: int,
810 microsecond: int,
811 tzinfo: tzinfo | None,
812 *_: Any,
813 **kwargs: Any,
814 ) -> datetime:
815 return datetime.__new__(
816 cls,
817 year,
818 month,
819 day,
820 hour,
821 minute,
822 second,
823 microsecond,
824 tzinfo=tzinfo,
825 **kwargs,
826 )
828 def __init__(
829 self,
830 year: int,
831 month: int,
832 day: int,
833 hour: int,
834 minute: int,
835 second: int,
836 microsecond: int,
837 tzinfo: tzinfo | None,
838 trivia: Trivia | None = None,
839 raw: str | None = None,
840 **kwargs: Any,
841 ) -> None:
842 super().__init__(trivia or Trivia())
844 self._raw = raw or self.isoformat()
846 def unwrap(self) -> datetime:
847 (
848 year,
849 month,
850 day,
851 hour,
852 minute,
853 second,
854 microsecond,
855 tzinfo,
856 _,
857 _,
858 ) = self._getstate()
859 return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
861 @property
862 def discriminant(self) -> int:
863 return 5
865 @property
866 def value(self) -> datetime:
867 return self
869 def as_string(self) -> str:
870 return self._raw
872 def __add__(self, other):
873 if PY38:
874 result = datetime(
875 self.year,
876 self.month,
877 self.day,
878 self.hour,
879 self.minute,
880 self.second,
881 self.microsecond,
882 self.tzinfo,
883 ).__add__(other)
884 else:
885 result = super().__add__(other)
887 return self._new(result)
889 def __sub__(self, other):
890 if PY38:
891 result = datetime(
892 self.year,
893 self.month,
894 self.day,
895 self.hour,
896 self.minute,
897 self.second,
898 self.microsecond,
899 self.tzinfo,
900 ).__sub__(other)
901 else:
902 result = super().__sub__(other)
904 if isinstance(result, datetime):
905 result = self._new(result)
907 return result
909 def replace(self, *args: Any, **kwargs: Any) -> datetime:
910 return self._new(super().replace(*args, **kwargs))
912 def astimezone(self, tz: tzinfo) -> datetime:
913 result = super().astimezone(tz)
914 if PY38:
915 return result
916 return self._new(result)
918 def _new(self, result) -> DateTime:
919 raw = result.isoformat()
921 return DateTime(
922 result.year,
923 result.month,
924 result.day,
925 result.hour,
926 result.minute,
927 result.second,
928 result.microsecond,
929 result.tzinfo,
930 self._trivia,
931 raw,
932 )
934 def _getstate(self, protocol=3):
935 return (
936 self.year,
937 self.month,
938 self.day,
939 self.hour,
940 self.minute,
941 self.second,
942 self.microsecond,
943 self.tzinfo,
944 self._trivia,
945 self._raw,
946 )
949class Date(Item, date):
950 """
951 A date literal.
952 """
954 def __new__(cls, year: int, month: int, day: int, *_: Any) -> date:
955 return date.__new__(cls, year, month, day)
957 def __init__(
958 self,
959 year: int,
960 month: int,
961 day: int,
962 trivia: Trivia | None = None,
963 raw: str = "",
964 ) -> None:
965 super().__init__(trivia or Trivia())
967 self._raw = raw
969 def unwrap(self) -> date:
970 (year, month, day, _, _) = self._getstate()
971 return date(year, month, day)
973 @property
974 def discriminant(self) -> int:
975 return 6
977 @property
978 def value(self) -> date:
979 return self
981 def as_string(self) -> str:
982 return self._raw
984 def __add__(self, other):
985 if PY38:
986 result = date(self.year, self.month, self.day).__add__(other)
987 else:
988 result = super().__add__(other)
990 return self._new(result)
992 def __sub__(self, other):
993 if PY38:
994 result = date(self.year, self.month, self.day).__sub__(other)
995 else:
996 result = super().__sub__(other)
998 if isinstance(result, date):
999 result = self._new(result)
1001 return result
1003 def replace(self, *args: Any, **kwargs: Any) -> date:
1004 return self._new(super().replace(*args, **kwargs))
1006 def _new(self, result):
1007 raw = result.isoformat()
1009 return Date(result.year, result.month, result.day, self._trivia, raw)
1011 def _getstate(self, protocol=3):
1012 return (self.year, self.month, self.day, self._trivia, self._raw)
1015class Time(Item, time):
1016 """
1017 A time literal.
1018 """
1020 def __new__(
1021 cls,
1022 hour: int,
1023 minute: int,
1024 second: int,
1025 microsecond: int,
1026 tzinfo: tzinfo | None,
1027 *_: Any,
1028 ) -> time:
1029 return time.__new__(cls, hour, minute, second, microsecond, tzinfo)
1031 def __init__(
1032 self,
1033 hour: int,
1034 minute: int,
1035 second: int,
1036 microsecond: int,
1037 tzinfo: tzinfo | None,
1038 trivia: Trivia | None = None,
1039 raw: str = "",
1040 ) -> None:
1041 super().__init__(trivia or Trivia())
1043 self._raw = raw
1045 def unwrap(self) -> time:
1046 (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
1047 return time(hour, minute, second, microsecond, tzinfo)
1049 @property
1050 def discriminant(self) -> int:
1051 return 7
1053 @property
1054 def value(self) -> time:
1055 return self
1057 def as_string(self) -> str:
1058 return self._raw
1060 def replace(self, *args: Any, **kwargs: Any) -> time:
1061 return self._new(super().replace(*args, **kwargs))
1063 def _new(self, result):
1064 raw = result.isoformat()
1066 return Time(
1067 result.hour,
1068 result.minute,
1069 result.second,
1070 result.microsecond,
1071 result.tzinfo,
1072 self._trivia,
1073 raw,
1074 )
1076 def _getstate(self, protocol: int = 3) -> tuple:
1077 return (
1078 self.hour,
1079 self.minute,
1080 self.second,
1081 self.microsecond,
1082 self.tzinfo,
1083 self._trivia,
1084 self._raw,
1085 )
1088class _ArrayItemGroup:
1089 __slots__ = ("comma", "comment", "indent", "value")
1091 def __init__(
1092 self,
1093 value: Item | None = None,
1094 indent: Whitespace | None = None,
1095 comma: Whitespace | None = None,
1096 comment: Comment | None = None,
1097 ) -> None:
1098 self.value = value
1099 self.indent = indent
1100 self.comma = comma
1101 self.comment = comment
1103 def __iter__(self) -> Iterator[Item]:
1104 return filter(
1105 lambda x: x is not None, (self.indent, self.value, self.comma, self.comment)
1106 )
1108 def __repr__(self) -> str:
1109 return repr(tuple(self))
1111 def is_whitespace(self) -> bool:
1112 return self.value is None and self.comment is None
1114 def __bool__(self) -> bool:
1115 try:
1116 next(iter(self))
1117 except StopIteration:
1118 return False
1119 return True
1122class Array(Item, _CustomList):
1123 """
1124 An array literal
1125 """
1127 def __init__(
1128 self, value: list[Item], trivia: Trivia, multiline: bool = False
1129 ) -> None:
1130 super().__init__(trivia)
1131 list.__init__(
1132 self,
1133 [v for v in value if not isinstance(v, (Whitespace, Comment, Null))],
1134 )
1135 self._index_map: dict[int, int] = {}
1136 self._value = self._group_values(value)
1137 self._multiline = multiline
1138 self._reindex()
1140 def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
1141 """Group the values into (indent, value, comma, comment) tuples"""
1142 groups = []
1143 this_group = _ArrayItemGroup()
1144 start_new_group = False
1145 for item in value:
1146 if isinstance(item, Whitespace):
1147 if "," not in item.s or start_new_group:
1148 groups.append(this_group)
1149 this_group = _ArrayItemGroup(indent=item)
1150 start_new_group = False
1151 else:
1152 if this_group.value is None:
1153 # when comma is met and no value is provided, add a dummy Null
1154 this_group.value = Null()
1155 this_group.comma = item
1156 elif isinstance(item, Comment):
1157 if this_group.value is None:
1158 this_group.value = Null()
1159 this_group.comment = item
1160 # Comments are the last item in a group.
1161 start_new_group = True
1162 elif this_group.value is None:
1163 this_group.value = item
1164 else:
1165 groups.append(this_group)
1166 this_group = _ArrayItemGroup(value=item)
1167 groups.append(this_group)
1168 return [group for group in groups if group]
1170 def unwrap(self) -> list[Any]:
1171 unwrapped = []
1172 for v in self:
1173 if hasattr(v, "unwrap"):
1174 unwrapped.append(v.unwrap())
1175 else:
1176 unwrapped.append(v)
1177 return unwrapped
1179 @property
1180 def discriminant(self) -> int:
1181 return 8
1183 @property
1184 def value(self) -> list:
1185 return self
1187 def _iter_items(self) -> Iterator[Item]:
1188 for v in self._value:
1189 yield from v
1191 def multiline(self, multiline: bool) -> Array:
1192 """Change the array to display in multiline or not.
1194 :Example:
1196 >>> a = item([1, 2, 3])
1197 >>> print(a.as_string())
1198 [1, 2, 3]
1199 >>> print(a.multiline(True).as_string())
1200 [
1201 1,
1202 2,
1203 3,
1204 ]
1205 """
1206 self._multiline = multiline
1208 return self
1210 def as_string(self) -> str:
1211 if not self._multiline or not self._value:
1212 return f"[{''.join(v.as_string() for v in self._iter_items())}]"
1214 s = "[\n"
1215 s += "".join(
1216 self.trivia.indent
1217 + " " * 4
1218 + v.value.as_string()
1219 + ("," if not isinstance(v.value, Null) else "")
1220 + (v.comment.as_string() if v.comment is not None else "")
1221 + "\n"
1222 for v in self._value
1223 if v.value is not None
1224 )
1225 s += self.trivia.indent + "]"
1227 return s
1229 def _reindex(self) -> None:
1230 self._index_map.clear()
1231 index = 0
1232 for i, v in enumerate(self._value):
1233 if v.value is None or isinstance(v.value, Null):
1234 continue
1235 self._index_map[index] = i
1236 index += 1
1238 def add_line(
1239 self,
1240 *items: Any,
1241 indent: str = " ",
1242 comment: str | None = None,
1243 add_comma: bool = True,
1244 newline: bool = True,
1245 ) -> None:
1246 """Add multiple items in a line to control the format precisely.
1247 When add_comma is True, only accept actual values and
1248 ", " will be added between values automatically.
1250 :Example:
1252 >>> a = array()
1253 >>> a.add_line(1, 2, 3)
1254 >>> a.add_line(4, 5, 6)
1255 >>> a.add_line(indent="")
1256 >>> print(a.as_string())
1257 [
1258 1, 2, 3,
1259 4, 5, 6,
1260 ]
1261 """
1262 new_values: list[Item] = []
1263 first_indent = f"\n{indent}" if newline else indent
1264 if first_indent:
1265 new_values.append(Whitespace(first_indent))
1266 whitespace = ""
1267 data_values = []
1268 for i, el in enumerate(items):
1269 it = item(el, _parent=self)
1270 if isinstance(it, Comment) or (add_comma and isinstance(el, Whitespace)):
1271 raise ValueError(f"item type {type(it)} is not allowed in add_line")
1272 if not isinstance(it, Whitespace):
1273 if whitespace:
1274 new_values.append(Whitespace(whitespace))
1275 whitespace = ""
1276 new_values.append(it)
1277 data_values.append(it.value)
1278 if add_comma:
1279 new_values.append(Whitespace(","))
1280 if i != len(items) - 1:
1281 new_values.append(Whitespace(" "))
1282 elif "," not in it.s:
1283 whitespace += it.s
1284 else:
1285 new_values.append(it)
1286 if whitespace:
1287 new_values.append(Whitespace(whitespace))
1288 if comment:
1289 indent = " " if items else ""
1290 new_values.append(
1291 Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
1292 )
1293 list.extend(self, data_values)
1294 if len(self._value) > 0:
1295 last_item = self._value[-1]
1296 last_value_item = next(
1297 (
1298 v
1299 for v in self._value[::-1]
1300 if v.value is not None and not isinstance(v.value, Null)
1301 ),
1302 None,
1303 )
1304 if last_value_item is not None:
1305 last_value_item.comma = Whitespace(",")
1306 if last_item.is_whitespace():
1307 self._value[-1:-1] = self._group_values(new_values)
1308 else:
1309 self._value.extend(self._group_values(new_values))
1310 else:
1311 self._value.extend(self._group_values(new_values))
1312 self._reindex()
1314 def clear(self) -> None:
1315 """Clear the array."""
1316 list.clear(self)
1317 self._index_map.clear()
1318 self._value.clear()
1320 def __len__(self) -> int:
1321 return list.__len__(self)
1323 def item(self, index: int) -> Item:
1324 rv = list.__getitem__(self, index)
1325 return cast(Item, rv)
1327 def __getitem__(self, key: int | slice) -> Any:
1328 rv = list.__getitem__(self, key)
1329 if isinstance(rv, Bool):
1330 return rv.value
1331 return rv
1333 def __setitem__(self, key: int | slice, value: Any) -> Any:
1334 it = item(value, _parent=self)
1335 list.__setitem__(self, key, it)
1336 if isinstance(key, slice):
1337 raise ValueError("slice assignment is not supported")
1338 if key < 0:
1339 key += len(self)
1340 self._value[self._index_map[key]].value = it
1342 def insert(self, pos: int, value: Any) -> None:
1343 it = item(value, _parent=self)
1344 length = len(self)
1345 if not isinstance(it, (Comment, Whitespace)):
1346 list.insert(self, pos, it)
1347 if pos < 0:
1348 pos += length
1349 if pos < 0:
1350 pos = 0
1352 idx = 0 # insert position of the self._value list
1353 default_indent = " "
1354 if pos < length:
1355 try:
1356 idx = self._index_map[pos]
1357 except KeyError as e:
1358 raise IndexError("list index out of range") from e
1359 else:
1360 idx = len(self._value)
1361 if idx >= 1 and self._value[idx - 1].is_whitespace():
1362 # The last item is a pure whitespace(\n ), insert before it
1363 idx -= 1
1364 if (
1365 self._value[idx].indent is not None
1366 and "\n" in self._value[idx].indent.s
1367 ):
1368 default_indent = "\n "
1369 indent: Item | None = None
1370 comma: Item | None = Whitespace(",") if pos < length else None
1371 if idx < len(self._value) and not self._value[idx].is_whitespace():
1372 # Prefer to copy the indentation from the item after
1373 indent = self._value[idx].indent
1374 if idx > 0:
1375 last_item = self._value[idx - 1]
1376 if indent is None:
1377 indent = last_item.indent
1378 if not isinstance(last_item.value, Null) and "\n" in default_indent:
1379 # Copy the comma from the last item if 1) it contains a value and
1380 # 2) the array is multiline
1381 comma = last_item.comma
1382 if last_item.comma is None and not isinstance(last_item.value, Null):
1383 # Add comma to the last item to separate it from the following items.
1384 last_item.comma = Whitespace(",")
1385 if indent is None and (idx > 0 or "\n" in default_indent):
1386 # apply default indent if it isn't the first item or the array is multiline.
1387 indent = Whitespace(default_indent)
1388 new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
1389 self._value.insert(idx, new_item)
1390 self._reindex()
1392 def __delitem__(self, key: int | slice):
1393 length = len(self)
1394 list.__delitem__(self, key)
1396 if isinstance(key, slice):
1397 indices_to_remove = list(
1398 range(key.start or 0, key.stop or length, key.step or 1)
1399 )
1400 else:
1401 indices_to_remove = [length + key if key < 0 else key]
1402 for i in sorted(indices_to_remove, reverse=True):
1403 try:
1404 idx = self._index_map[i]
1405 except KeyError as e:
1406 if not isinstance(key, slice):
1407 raise IndexError("list index out of range") from e
1408 else:
1409 group_rm = self._value[idx]
1410 del self._value[idx]
1411 if (
1412 idx == 0
1413 and len(self._value) > 0
1414 and self._value[idx].indent
1415 and "\n" not in self._value[idx].indent.s
1416 ):
1417 # Remove the indentation of the first item if not newline
1418 self._value[idx].indent = None
1419 comma_in_indent = (
1420 group_rm.indent is not None and "," in group_rm.indent.s
1421 )
1422 comma_in_comma = group_rm.comma is not None and "," in group_rm.comma.s
1423 if comma_in_indent and comma_in_comma:
1424 # Removed group had both commas. Add one to the next group.
1425 group = self._value[idx] if len(self._value) > idx else None
1426 if group is not None:
1427 if group.indent is None:
1428 group.indent = Whitespace(",")
1429 elif "," not in group.indent.s:
1430 # Insert the comma after the newline
1431 try:
1432 newline_index = group.indent.s.index("\n")
1433 group.indent._s = (
1434 group.indent.s[: newline_index + 1]
1435 + ","
1436 + group.indent.s[newline_index + 1 :]
1437 )
1438 except ValueError:
1439 group.indent._s = "," + group.indent.s
1440 elif not comma_in_indent and not comma_in_comma:
1441 # Removed group had no commas. Remove the next comma found.
1442 for j in range(idx, len(self._value)):
1443 group = self._value[j]
1444 if group.indent is not None and "," in group.indent.s:
1445 group.indent._s = group.indent.s.replace(",", "", 1)
1446 break
1447 if group_rm.indent is not None and "\n" in group_rm.indent.s:
1448 # Restore the removed group's newline onto the next group
1449 # if the next group does not have a newline.
1450 # i.e. the two were on the same line
1451 group = self._value[idx] if len(self._value) > idx else None
1452 if group is not None and (
1453 group.indent is None or "\n" not in group.indent.s
1454 ):
1455 group.indent = group_rm.indent
1457 if len(self._value) > 0:
1458 v = self._value[-1]
1459 if not v.is_whitespace():
1460 # remove the comma of the last item
1461 v.comma = None
1463 self._reindex()
1465 def _getstate(self, protocol=3):
1466 return list(self._iter_items()), self._trivia, self._multiline
1469class AbstractTable(Item, _CustomDict):
1470 """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
1472 def __init__(self, value: container.Container, trivia: Trivia):
1473 Item.__init__(self, trivia)
1475 self._value = value
1477 for k, v in self._value.body:
1478 if k is not None:
1479 dict.__setitem__(self, k.key, v)
1481 def unwrap(self) -> dict[str, Any]:
1482 unwrapped = {}
1483 for k, v in self.items():
1484 if isinstance(k, Key):
1485 k = k.key
1486 if hasattr(v, "unwrap"):
1487 v = v.unwrap()
1488 unwrapped[k] = v
1490 return unwrapped
1492 @property
1493 def value(self) -> container.Container:
1494 return self._value
1496 @overload
1497 def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ...
1499 @overload
1500 def append(self: AT, key: Key | str, value: Any) -> AT: ...
1502 def append(self, key, value):
1503 raise NotImplementedError
1505 @overload
1506 def add(self: AT, key: Comment | Whitespace) -> AT: ...
1508 @overload
1509 def add(self: AT, key: Key | str, value: Any = ...) -> AT: ...
1511 def add(self, key, value=None):
1512 if value is None:
1513 if not isinstance(key, (Comment, Whitespace)):
1514 msg = "Non comment/whitespace items must have an associated key"
1515 raise ValueError(msg)
1517 key, value = None, key
1519 return self.append(key, value)
1521 def remove(self: AT, key: Key | str) -> AT:
1522 self._value.remove(key)
1524 if isinstance(key, Key):
1525 key = key.key
1527 if key is not None:
1528 dict.__delitem__(self, key)
1530 return self
1532 def item(self, key: Key | str) -> Item:
1533 return self._value.item(key)
1535 def setdefault(self, key: Key | str, default: Any) -> Any:
1536 super().setdefault(key, default)
1537 return self[key]
1539 def __str__(self):
1540 return str(self.value)
1542 def copy(self: AT) -> AT:
1543 return copy.copy(self)
1545 def __repr__(self) -> str:
1546 return repr(self.value)
1548 def __iter__(self) -> Iterator[str]:
1549 return iter(self._value)
1551 def __len__(self) -> int:
1552 return len(self._value)
1554 def __delitem__(self, key: Key | str) -> None:
1555 self.remove(key)
1557 def __getitem__(self, key: Key | str) -> Item:
1558 return cast(Item, self._value[key])
1560 def __setitem__(self, key: Key | str, value: Any) -> None:
1561 if not isinstance(value, Item):
1562 value = item(value, _parent=self)
1564 is_replace = key in self
1565 self._value[key] = value
1567 if key is not None:
1568 dict.__setitem__(self, key, value)
1570 if is_replace:
1571 return
1572 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1573 if not m:
1574 return
1576 indent = m.group(1)
1578 if not isinstance(value, Whitespace):
1579 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1580 if not m:
1581 value.trivia.indent = indent
1582 else:
1583 value.trivia.indent = m.group(1) + indent + m.group(2)
1586class Table(AbstractTable):
1587 """
1588 A table literal.
1589 """
1591 def __init__(
1592 self,
1593 value: container.Container,
1594 trivia: Trivia,
1595 is_aot_element: bool,
1596 is_super_table: bool | None = None,
1597 name: str | None = None,
1598 display_name: str | None = None,
1599 ) -> None:
1600 super().__init__(value, trivia)
1602 self.name = name
1603 self.display_name = display_name
1604 self._is_aot_element = is_aot_element
1605 self._is_super_table = is_super_table
1607 @property
1608 def discriminant(self) -> int:
1609 return 9
1611 def __copy__(self) -> Table:
1612 return type(self)(
1613 self._value.copy(),
1614 self._trivia.copy(),
1615 self._is_aot_element,
1616 self._is_super_table,
1617 self.name,
1618 self.display_name,
1619 )
1621 def append(self, key: Key | str | None, _item: Any) -> Table:
1622 """
1623 Appends a (key, item) to the table.
1624 """
1625 if not isinstance(_item, Item):
1626 _item = item(_item, _parent=self)
1628 self._value.append(key, _item)
1630 if isinstance(key, Key):
1631 key = next(iter(key)).key
1632 _item = self._value[key]
1634 if key is not None:
1635 dict.__setitem__(self, key, _item)
1637 m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1638 if not m:
1639 return self
1641 indent = m.group(1)
1643 if not isinstance(_item, Whitespace):
1644 m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
1645 if not m:
1646 _item.trivia.indent = indent
1647 else:
1648 _item.trivia.indent = m.group(1) + indent + m.group(2)
1650 return self
1652 def raw_append(self, key: Key | str | None, _item: Any) -> Table:
1653 """Similar to :meth:`append` but does not copy indentation."""
1654 if not isinstance(_item, Item):
1655 _item = item(_item)
1657 self._value.append(key, _item, validate=False)
1659 if isinstance(key, Key):
1660 key = next(iter(key)).key
1661 _item = self._value[key]
1663 if key is not None:
1664 dict.__setitem__(self, key, _item)
1666 return self
1668 def is_aot_element(self) -> bool:
1669 """True if the table is the direct child of an AOT element."""
1670 return self._is_aot_element
1672 def is_super_table(self) -> bool:
1673 """A super table is the intermediate parent of a nested table as in [a.b.c].
1674 If true, it won't appear in the TOML representation."""
1675 if self._is_super_table is not None:
1676 return self._is_super_table
1677 if not self:
1678 return False
1679 # If the table has children and all children are tables, then it is a super table.
1680 for k, child in self.items():
1681 if not isinstance(k, Key):
1682 k = SingleKey(k)
1683 index = self.value._map[k]
1684 if isinstance(index, tuple):
1685 return False
1686 real_key = self.value.body[index][0]
1687 if (
1688 not isinstance(child, (Table, AoT))
1689 or real_key is None
1690 or real_key.is_dotted()
1691 ):
1692 return False
1693 return True
1695 def as_string(self) -> str:
1696 return self._value.as_string()
1698 # Helpers
1700 def indent(self, indent: int) -> Table:
1701 """Indent the table with given number of spaces."""
1702 super().indent(indent)
1704 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1705 if not m:
1706 indent_str = ""
1707 else:
1708 indent_str = m.group(1)
1710 for _, item in self._value.body:
1711 if not isinstance(item, Whitespace):
1712 item.trivia.indent = indent_str + item.trivia.indent
1714 return self
1716 def invalidate_display_name(self):
1717 """Call ``invalidate_display_name`` on the contained tables"""
1718 self.display_name = None
1720 for child in self.values():
1721 if hasattr(child, "invalidate_display_name"):
1722 child.invalidate_display_name()
1724 def _getstate(self, protocol: int = 3) -> tuple:
1725 return (
1726 self._value,
1727 self._trivia,
1728 self._is_aot_element,
1729 self._is_super_table,
1730 self.name,
1731 self.display_name,
1732 )
1735class InlineTable(AbstractTable):
1736 """
1737 An inline table literal.
1738 """
1740 def __init__(
1741 self, value: container.Container, trivia: Trivia, new: bool = False
1742 ) -> None:
1743 super().__init__(value, trivia)
1745 self._new = new
1747 @property
1748 def discriminant(self) -> int:
1749 return 10
1751 def append(self, key: Key | str | None, _item: Any) -> InlineTable:
1752 """
1753 Appends a (key, item) to the table.
1754 """
1755 if not isinstance(_item, Item):
1756 _item = item(_item, _parent=self)
1758 if not isinstance(_item, (Whitespace, Comment)):
1759 if not _item.trivia.indent and len(self._value) > 0 and not self._new:
1760 _item.trivia.indent = " "
1761 if _item.trivia.comment:
1762 _item.trivia.comment = ""
1764 self._value.append(key, _item)
1766 if isinstance(key, Key):
1767 key = key.key
1769 if key is not None:
1770 dict.__setitem__(self, key, _item)
1772 return self
1774 def as_string(self) -> str:
1775 buf = "{"
1776 last_item_idx = next(
1777 (
1778 i
1779 for i in range(len(self._value.body) - 1, -1, -1)
1780 if self._value.body[i][0] is not None
1781 ),
1782 None,
1783 )
1784 for i, (k, v) in enumerate(self._value.body):
1785 if k is None:
1786 if i == len(self._value.body) - 1:
1787 if self._new:
1788 buf = buf.rstrip(", ")
1789 else:
1790 buf = buf.rstrip(",")
1792 buf += v.as_string()
1794 continue
1796 v_trivia_trail = v.trivia.trail.replace("\n", "")
1797 buf += (
1798 f"{v.trivia.indent}"
1799 f"{k.as_string() + ('.' if k.is_dotted() else '')}"
1800 f"{k.sep}"
1801 f"{v.as_string()}"
1802 f"{v.trivia.comment}"
1803 f"{v_trivia_trail}"
1804 )
1806 if last_item_idx is not None and i < last_item_idx:
1807 buf += ","
1808 if self._new:
1809 buf += " "
1811 buf += "}"
1813 return buf
1815 def __setitem__(self, key: Key | str, value: Any) -> None:
1816 if hasattr(value, "trivia") and value.trivia.comment:
1817 value.trivia.comment = ""
1818 super().__setitem__(key, value)
1820 def __copy__(self) -> InlineTable:
1821 return type(self)(self._value.copy(), self._trivia.copy(), self._new)
1823 def _getstate(self, protocol: int = 3) -> tuple:
1824 return (self._value, self._trivia)
1827class String(str, Item):
1828 """
1829 A string literal.
1830 """
1832 def __new__(cls, t, value, original, trivia):
1833 return super().__new__(cls, value)
1835 def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
1836 super().__init__(trivia)
1838 self._t = t
1839 self._original = original
1841 def unwrap(self) -> str:
1842 return str(self)
1844 @property
1845 def discriminant(self) -> int:
1846 return 11
1848 @property
1849 def value(self) -> str:
1850 return self
1852 def as_string(self) -> str:
1853 return f"{self._t.value}{decode(self._original)}{self._t.value}"
1855 def __add__(self: ItemT, other: str) -> ItemT:
1856 if not isinstance(other, str):
1857 return NotImplemented
1858 result = super().__add__(other)
1859 original = self._original + getattr(other, "_original", other)
1861 return self._new(result, original)
1863 def _new(self, result: str, original: str) -> String:
1864 return String(self._t, result, original, self._trivia)
1866 def _getstate(self, protocol=3):
1867 return self._t, str(self), self._original, self._trivia
1869 @classmethod
1870 def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String:
1871 value = decode(value)
1873 invalid = type_.invalid_sequences
1874 if any(c in value for c in invalid):
1875 raise InvalidStringError(value, invalid, type_.value)
1877 escaped = type_.escaped_sequences
1878 string_value = escape_string(value, escaped) if escape and escaped else value
1880 return cls(type_, decode(value), string_value, Trivia())
1883class AoT(Item, _CustomList):
1884 """
1885 An array of table literal
1886 """
1888 def __init__(
1889 self, body: list[Table], name: str | None = None, parsed: bool = False
1890 ) -> None:
1891 self.name = name
1892 self._body: list[Table] = []
1893 self._parsed = parsed
1895 super().__init__(Trivia(trail=""))
1897 for table in body:
1898 self.append(table)
1900 def unwrap(self) -> list[dict[str, Any]]:
1901 unwrapped = []
1902 for t in self._body:
1903 if hasattr(t, "unwrap"):
1904 unwrapped.append(t.unwrap())
1905 else:
1906 unwrapped.append(t)
1907 return unwrapped
1909 @property
1910 def body(self) -> list[Table]:
1911 return self._body
1913 @property
1914 def discriminant(self) -> int:
1915 return 12
1917 @property
1918 def value(self) -> list[dict[Any, Any]]:
1919 return [v.value for v in self._body]
1921 def __len__(self) -> int:
1922 return len(self._body)
1924 @overload
1925 def __getitem__(self, key: slice) -> list[Table]: ...
1927 @overload
1928 def __getitem__(self, key: int) -> Table: ...
1930 def __getitem__(self, key):
1931 return self._body[key]
1933 def __setitem__(self, key: slice | int, value: Any) -> None:
1934 self._body[key] = item(value, _parent=self)
1936 def __delitem__(self, key: slice | int) -> None:
1937 del self._body[key]
1938 list.__delitem__(self, key)
1940 def insert(self, index: int, value: dict) -> None:
1941 value = item(value, _parent=self)
1942 if not isinstance(value, Table):
1943 raise ValueError(f"Unsupported insert value type: {type(value)}")
1944 length = len(self)
1945 if index < 0:
1946 index += length
1947 if index < 0:
1948 index = 0
1949 elif index >= length:
1950 index = length
1951 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1952 if m:
1953 indent = m.group(1)
1955 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1956 if not m:
1957 value.trivia.indent = indent
1958 else:
1959 value.trivia.indent = m.group(1) + indent + m.group(2)
1960 prev_table = self._body[index - 1] if 0 < index and length else None
1961 next_table = self._body[index + 1] if index < length - 1 else None
1962 if not self._parsed:
1963 if prev_table and "\n" not in value.trivia.indent:
1964 value.trivia.indent = "\n" + value.trivia.indent
1965 if next_table and "\n" not in next_table.trivia.indent:
1966 next_table.trivia.indent = "\n" + next_table.trivia.indent
1967 self._body.insert(index, value)
1968 list.insert(self, index, value)
1970 def invalidate_display_name(self):
1971 """Call ``invalidate_display_name`` on the contained tables"""
1972 for child in self:
1973 if hasattr(child, "invalidate_display_name"):
1974 child.invalidate_display_name()
1976 def as_string(self) -> str:
1977 b = ""
1978 for table in self._body:
1979 b += table.as_string()
1981 return b
1983 def __repr__(self) -> str:
1984 return f"<AoT {self.value}>"
1986 def _getstate(self, protocol=3):
1987 return self._body, self.name, self._parsed
1990class Null(Item):
1991 """
1992 A null item.
1993 """
1995 def __init__(self) -> None:
1996 super().__init__(Trivia(trail=""))
1998 def unwrap(self) -> None:
1999 return None
2001 @property
2002 def discriminant(self) -> int:
2003 return -1
2005 @property
2006 def value(self) -> None:
2007 return None
2009 def as_string(self) -> str:
2010 return ""
2012 def _getstate(self, protocol=3) -> tuple:
2013 return ()