Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tomlkit/items.py: 71%
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 t is None:
384 if not k or any(
385 c not in string.ascii_letters + string.digits + "-" + "_" for c in k
386 ):
387 t = KeyType.Basic
388 else:
389 t = KeyType.Bare
391 self.t = t
392 if sep is None:
393 sep = " = "
395 self.sep = sep
396 self.key = k
397 if original is None:
398 key_str = escape_string(k) if t == KeyType.Basic else k
399 original = f"{t.value}{key_str}{t.value}"
401 self._original = original
402 self._keys = [self]
403 self._dotted = False
405 @property
406 def delimiter(self) -> str:
407 """The delimiter: double quote/single quote/none"""
408 return self.t.value
410 def is_bare(self) -> bool:
411 """Check if the key is bare"""
412 return self.t == KeyType.Bare
414 def __hash__(self) -> int:
415 return hash(self.key)
417 def __eq__(self, other: Any) -> bool:
418 if isinstance(other, Key):
419 return isinstance(other, SingleKey) and self.key == other.key
421 return self.key == other
424class DottedKey(Key):
425 def __init__(
426 self,
427 keys: Iterable[SingleKey],
428 sep: str | None = None,
429 original: str | None = None,
430 ) -> None:
431 self._keys = list(keys)
432 if original is None:
433 original = ".".join(k.as_string() for k in self._keys)
435 self.sep = " = " if sep is None else sep
436 self._original = original
437 self._dotted = False
438 self.key = ".".join(k.key for k in self._keys)
440 def __hash__(self) -> int:
441 return hash(tuple(self._keys))
443 def __eq__(self, __o: object) -> bool:
444 return isinstance(__o, DottedKey) and self._keys == __o._keys
447class Item:
448 """
449 An item within a TOML document.
450 """
452 def __init__(self, trivia: Trivia) -> None:
453 self._trivia = trivia
455 @property
456 def trivia(self) -> Trivia:
457 """The trivia element associated with this item"""
458 return self._trivia
460 @property
461 def discriminant(self) -> int:
462 raise NotImplementedError()
464 def as_string(self) -> str:
465 """The TOML representation"""
466 raise NotImplementedError()
468 @property
469 def value(self) -> Any:
470 return self
472 def unwrap(self) -> Any:
473 """Returns as pure python object (ppo)"""
474 raise NotImplementedError()
476 # Helpers
478 def comment(self, comment: str) -> Item:
479 """Attach a comment to this item"""
480 if not comment.strip().startswith("#"):
481 comment = "# " + comment
483 self._trivia.comment_ws = " "
484 self._trivia.comment = comment
486 return self
488 def indent(self, indent: int) -> Item:
489 """Indent this item with given number of spaces"""
490 if self._trivia.indent.startswith("\n"):
491 self._trivia.indent = "\n" + " " * indent
492 else:
493 self._trivia.indent = " " * indent
495 return self
497 def is_boolean(self) -> bool:
498 return isinstance(self, Bool)
500 def is_table(self) -> bool:
501 return isinstance(self, Table)
503 def is_inline_table(self) -> bool:
504 return isinstance(self, InlineTable)
506 def is_aot(self) -> bool:
507 return isinstance(self, AoT)
509 def _getstate(self, protocol=3):
510 return (self._trivia,)
512 def __reduce__(self):
513 return self.__reduce_ex__(2)
515 def __reduce_ex__(self, protocol):
516 return self.__class__, self._getstate(protocol)
519class Whitespace(Item):
520 """
521 A whitespace literal.
522 """
524 def __init__(self, s: str, fixed: bool = False) -> None:
525 self._s = s
526 self._fixed = fixed
528 @property
529 def s(self) -> str:
530 return self._s
532 @property
533 def value(self) -> str:
534 """The wrapped string of the whitespace"""
535 return self._s
537 @property
538 def trivia(self) -> Trivia:
539 raise RuntimeError("Called trivia on a Whitespace variant.")
541 @property
542 def discriminant(self) -> int:
543 return 0
545 def is_fixed(self) -> bool:
546 """If the whitespace is fixed, it can't be merged or discarded from the output."""
547 return self._fixed
549 def as_string(self) -> str:
550 return self._s
552 def __repr__(self) -> str:
553 return f"<{self.__class__.__name__} {self._s!r}>"
555 def _getstate(self, protocol=3):
556 return self._s, self._fixed
559class Comment(Item):
560 """
561 A comment literal.
562 """
564 @property
565 def discriminant(self) -> int:
566 return 1
568 def as_string(self) -> str:
569 return (
570 f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
571 )
573 def __str__(self) -> str:
574 return f"{self._trivia.indent}{decode(self._trivia.comment)}"
577class Integer(Item, _CustomInt):
578 """
579 An integer literal.
580 """
582 def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
583 return int.__new__(cls, value)
585 def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
586 super().__init__(trivia)
587 self._original = value
588 self._raw = raw
589 self._sign = False
591 if re.match(r"^[+\-]\d+$", raw):
592 self._sign = True
594 def unwrap(self) -> int:
595 return self._original
597 __int__ = unwrap
599 def __hash__(self) -> int:
600 return hash(self.unwrap())
602 @property
603 def discriminant(self) -> int:
604 return 2
606 @property
607 def value(self) -> int:
608 """The wrapped integer value"""
609 return self
611 def as_string(self) -> str:
612 return self._raw
614 def _new(self, result):
615 raw = str(result)
616 if self._sign and result >= 0:
617 raw = f"+{raw}"
619 return Integer(result, self._trivia, raw)
621 def _getstate(self, protocol=3):
622 return int(self), self._trivia, self._raw
624 # int methods
625 __abs__ = wrap_method(int.__abs__)
626 __add__ = wrap_method(int.__add__)
627 __and__ = wrap_method(int.__and__)
628 __ceil__ = wrap_method(int.__ceil__)
629 __eq__ = int.__eq__
630 __floor__ = wrap_method(int.__floor__)
631 __floordiv__ = wrap_method(int.__floordiv__)
632 __invert__ = wrap_method(int.__invert__)
633 __le__ = int.__le__
634 __lshift__ = wrap_method(int.__lshift__)
635 __lt__ = int.__lt__
636 __mod__ = wrap_method(int.__mod__)
637 __mul__ = wrap_method(int.__mul__)
638 __neg__ = wrap_method(int.__neg__)
639 __or__ = wrap_method(int.__or__)
640 __pos__ = wrap_method(int.__pos__)
641 __pow__ = wrap_method(int.__pow__)
642 __radd__ = wrap_method(int.__radd__)
643 __rand__ = wrap_method(int.__rand__)
644 __rfloordiv__ = wrap_method(int.__rfloordiv__)
645 __rlshift__ = wrap_method(int.__rlshift__)
646 __rmod__ = wrap_method(int.__rmod__)
647 __rmul__ = wrap_method(int.__rmul__)
648 __ror__ = wrap_method(int.__ror__)
649 __round__ = wrap_method(int.__round__)
650 __rpow__ = wrap_method(int.__rpow__)
651 __rrshift__ = wrap_method(int.__rrshift__)
652 __rshift__ = wrap_method(int.__rshift__)
653 __rxor__ = wrap_method(int.__rxor__)
654 __trunc__ = wrap_method(int.__trunc__)
655 __xor__ = wrap_method(int.__xor__)
657 def __rtruediv__(self, other):
658 result = int.__rtruediv__(self, other)
659 if result is NotImplemented:
660 return result
661 return Float._new(self, result)
663 def __truediv__(self, other):
664 result = int.__truediv__(self, other)
665 if result is NotImplemented:
666 return result
667 return Float._new(self, result)
670class Float(Item, _CustomFloat):
671 """
672 A float literal.
673 """
675 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
676 return float.__new__(cls, value)
678 def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
679 super().__init__(trivia)
680 self._original = value
681 self._raw = raw
682 self._sign = False
684 if re.match(r"^[+\-].+$", raw):
685 self._sign = True
687 def unwrap(self) -> float:
688 return self._original
690 __float__ = unwrap
692 def __hash__(self) -> int:
693 return hash(self.unwrap())
695 @property
696 def discriminant(self) -> int:
697 return 3
699 @property
700 def value(self) -> float:
701 """The wrapped float value"""
702 return self
704 def as_string(self) -> str:
705 return self._raw
707 def _new(self, result):
708 raw = str(result)
710 if self._sign and result >= 0:
711 raw = f"+{raw}"
713 return Float(result, self._trivia, raw)
715 def _getstate(self, protocol=3):
716 return float(self), self._trivia, self._raw
718 # float methods
719 __abs__ = wrap_method(float.__abs__)
720 __add__ = wrap_method(float.__add__)
721 __eq__ = float.__eq__
722 __floordiv__ = wrap_method(float.__floordiv__)
723 __le__ = float.__le__
724 __lt__ = float.__lt__
725 __mod__ = wrap_method(float.__mod__)
726 __mul__ = wrap_method(float.__mul__)
727 __neg__ = wrap_method(float.__neg__)
728 __pos__ = wrap_method(float.__pos__)
729 __pow__ = wrap_method(float.__pow__)
730 __radd__ = wrap_method(float.__radd__)
731 __rfloordiv__ = wrap_method(float.__rfloordiv__)
732 __rmod__ = wrap_method(float.__rmod__)
733 __rmul__ = wrap_method(float.__rmul__)
734 __round__ = wrap_method(float.__round__)
735 __rpow__ = wrap_method(float.__rpow__)
736 __rtruediv__ = wrap_method(float.__rtruediv__)
737 __truediv__ = wrap_method(float.__truediv__)
738 __trunc__ = float.__trunc__
740 if sys.version_info >= (3, 9):
741 __ceil__ = float.__ceil__
742 __floor__ = float.__floor__
743 else:
744 __ceil__ = math.ceil
745 __floor__ = math.floor
748class Bool(Item):
749 """
750 A boolean literal.
751 """
753 def __init__(self, t: int, trivia: Trivia) -> None:
754 super().__init__(trivia)
756 self._value = bool(t)
758 def unwrap(self) -> bool:
759 return bool(self)
761 @property
762 def discriminant(self) -> int:
763 return 4
765 @property
766 def value(self) -> bool:
767 """The wrapped boolean value"""
768 return self._value
770 def as_string(self) -> str:
771 return str(self._value).lower()
773 def _getstate(self, protocol=3):
774 return self._value, self._trivia
776 def __bool__(self):
777 return self._value
779 __nonzero__ = __bool__
781 def __eq__(self, other):
782 if not isinstance(other, bool):
783 return NotImplemented
785 return other == self._value
787 def __hash__(self):
788 return hash(self._value)
790 def __repr__(self):
791 return repr(self._value)
794class DateTime(Item, datetime):
795 """
796 A datetime literal.
797 """
799 def __new__(
800 cls,
801 year: int,
802 month: int,
803 day: int,
804 hour: int,
805 minute: int,
806 second: int,
807 microsecond: int,
808 tzinfo: tzinfo | None,
809 *_: Any,
810 **kwargs: Any,
811 ) -> datetime:
812 return datetime.__new__(
813 cls,
814 year,
815 month,
816 day,
817 hour,
818 minute,
819 second,
820 microsecond,
821 tzinfo=tzinfo,
822 **kwargs,
823 )
825 def __init__(
826 self,
827 year: int,
828 month: int,
829 day: int,
830 hour: int,
831 minute: int,
832 second: int,
833 microsecond: int,
834 tzinfo: tzinfo | None,
835 trivia: Trivia | None = None,
836 raw: str | None = None,
837 **kwargs: Any,
838 ) -> None:
839 super().__init__(trivia or Trivia())
841 self._raw = raw or self.isoformat()
843 def unwrap(self) -> datetime:
844 (
845 year,
846 month,
847 day,
848 hour,
849 minute,
850 second,
851 microsecond,
852 tzinfo,
853 _,
854 _,
855 ) = self._getstate()
856 return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
858 @property
859 def discriminant(self) -> int:
860 return 5
862 @property
863 def value(self) -> datetime:
864 return self
866 def as_string(self) -> str:
867 return self._raw
869 def __add__(self, other):
870 if PY38:
871 result = datetime(
872 self.year,
873 self.month,
874 self.day,
875 self.hour,
876 self.minute,
877 self.second,
878 self.microsecond,
879 self.tzinfo,
880 ).__add__(other)
881 else:
882 result = super().__add__(other)
884 return self._new(result)
886 def __sub__(self, other):
887 if PY38:
888 result = datetime(
889 self.year,
890 self.month,
891 self.day,
892 self.hour,
893 self.minute,
894 self.second,
895 self.microsecond,
896 self.tzinfo,
897 ).__sub__(other)
898 else:
899 result = super().__sub__(other)
901 if isinstance(result, datetime):
902 result = self._new(result)
904 return result
906 def replace(self, *args: Any, **kwargs: Any) -> datetime:
907 return self._new(super().replace(*args, **kwargs))
909 def astimezone(self, tz: tzinfo) -> datetime:
910 result = super().astimezone(tz)
911 if PY38:
912 return result
913 return self._new(result)
915 def _new(self, result) -> DateTime:
916 raw = result.isoformat()
918 return DateTime(
919 result.year,
920 result.month,
921 result.day,
922 result.hour,
923 result.minute,
924 result.second,
925 result.microsecond,
926 result.tzinfo,
927 self._trivia,
928 raw,
929 )
931 def _getstate(self, protocol=3):
932 return (
933 self.year,
934 self.month,
935 self.day,
936 self.hour,
937 self.minute,
938 self.second,
939 self.microsecond,
940 self.tzinfo,
941 self._trivia,
942 self._raw,
943 )
946class Date(Item, date):
947 """
948 A date literal.
949 """
951 def __new__(cls, year: int, month: int, day: int, *_: Any) -> date:
952 return date.__new__(cls, year, month, day)
954 def __init__(
955 self,
956 year: int,
957 month: int,
958 day: int,
959 trivia: Trivia | None = None,
960 raw: str = "",
961 ) -> None:
962 super().__init__(trivia or Trivia())
964 self._raw = raw
966 def unwrap(self) -> date:
967 (year, month, day, _, _) = self._getstate()
968 return date(year, month, day)
970 @property
971 def discriminant(self) -> int:
972 return 6
974 @property
975 def value(self) -> date:
976 return self
978 def as_string(self) -> str:
979 return self._raw
981 def __add__(self, other):
982 if PY38:
983 result = date(self.year, self.month, self.day).__add__(other)
984 else:
985 result = super().__add__(other)
987 return self._new(result)
989 def __sub__(self, other):
990 if PY38:
991 result = date(self.year, self.month, self.day).__sub__(other)
992 else:
993 result = super().__sub__(other)
995 if isinstance(result, date):
996 result = self._new(result)
998 return result
1000 def replace(self, *args: Any, **kwargs: Any) -> date:
1001 return self._new(super().replace(*args, **kwargs))
1003 def _new(self, result):
1004 raw = result.isoformat()
1006 return Date(result.year, result.month, result.day, self._trivia, raw)
1008 def _getstate(self, protocol=3):
1009 return (self.year, self.month, self.day, self._trivia, self._raw)
1012class Time(Item, time):
1013 """
1014 A time literal.
1015 """
1017 def __new__(
1018 cls,
1019 hour: int,
1020 minute: int,
1021 second: int,
1022 microsecond: int,
1023 tzinfo: tzinfo | None,
1024 *_: Any,
1025 ) -> time:
1026 return time.__new__(cls, hour, minute, second, microsecond, tzinfo)
1028 def __init__(
1029 self,
1030 hour: int,
1031 minute: int,
1032 second: int,
1033 microsecond: int,
1034 tzinfo: tzinfo | None,
1035 trivia: Trivia | None = None,
1036 raw: str = "",
1037 ) -> None:
1038 super().__init__(trivia or Trivia())
1040 self._raw = raw
1042 def unwrap(self) -> time:
1043 (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
1044 return time(hour, minute, second, microsecond, tzinfo)
1046 @property
1047 def discriminant(self) -> int:
1048 return 7
1050 @property
1051 def value(self) -> time:
1052 return self
1054 def as_string(self) -> str:
1055 return self._raw
1057 def replace(self, *args: Any, **kwargs: Any) -> time:
1058 return self._new(super().replace(*args, **kwargs))
1060 def _new(self, result):
1061 raw = result.isoformat()
1063 return Time(
1064 result.hour,
1065 result.minute,
1066 result.second,
1067 result.microsecond,
1068 result.tzinfo,
1069 self._trivia,
1070 raw,
1071 )
1073 def _getstate(self, protocol: int = 3) -> tuple:
1074 return (
1075 self.hour,
1076 self.minute,
1077 self.second,
1078 self.microsecond,
1079 self.tzinfo,
1080 self._trivia,
1081 self._raw,
1082 )
1085class _ArrayItemGroup:
1086 __slots__ = ("value", "indent", "comma", "comment")
1088 def __init__(
1089 self,
1090 value: Item | None = None,
1091 indent: Whitespace | None = None,
1092 comma: Whitespace | None = None,
1093 comment: Comment | None = None,
1094 ) -> None:
1095 self.value = value
1096 self.indent = indent
1097 self.comma = comma
1098 self.comment = comment
1100 def __iter__(self) -> Iterator[Item]:
1101 return filter(
1102 lambda x: x is not None, (self.indent, self.value, self.comma, self.comment)
1103 )
1105 def __repr__(self) -> str:
1106 return repr(tuple(self))
1108 def is_whitespace(self) -> bool:
1109 return self.value is None and self.comment is None
1111 def __bool__(self) -> bool:
1112 try:
1113 next(iter(self))
1114 except StopIteration:
1115 return False
1116 return True
1119class Array(Item, _CustomList):
1120 """
1121 An array literal
1122 """
1124 def __init__(
1125 self, value: list[Item], trivia: Trivia, multiline: bool = False
1126 ) -> None:
1127 super().__init__(trivia)
1128 list.__init__(
1129 self,
1130 [v for v in value if not isinstance(v, (Whitespace, Comment, Null))],
1131 )
1132 self._index_map: dict[int, int] = {}
1133 self._value = self._group_values(value)
1134 self._multiline = multiline
1135 self._reindex()
1137 def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
1138 """Group the values into (indent, value, comma, comment) tuples"""
1139 groups = []
1140 this_group = _ArrayItemGroup()
1141 for item in value:
1142 if isinstance(item, Whitespace):
1143 if "," not in item.s:
1144 groups.append(this_group)
1145 this_group = _ArrayItemGroup(indent=item)
1146 else:
1147 if this_group.value is None:
1148 # when comma is met and no value is provided, add a dummy Null
1149 this_group.value = Null()
1150 this_group.comma = item
1151 elif isinstance(item, Comment):
1152 if this_group.value is None:
1153 this_group.value = Null()
1154 this_group.comment = item
1155 elif this_group.value is None:
1156 this_group.value = item
1157 else:
1158 groups.append(this_group)
1159 this_group = _ArrayItemGroup(value=item)
1160 groups.append(this_group)
1161 return [group for group in groups if group]
1163 def unwrap(self) -> list[Any]:
1164 unwrapped = []
1165 for v in self:
1166 if hasattr(v, "unwrap"):
1167 unwrapped.append(v.unwrap())
1168 else:
1169 unwrapped.append(v)
1170 return unwrapped
1172 @property
1173 def discriminant(self) -> int:
1174 return 8
1176 @property
1177 def value(self) -> list:
1178 return self
1180 def _iter_items(self) -> Iterator[Item]:
1181 for v in self._value:
1182 yield from v
1184 def multiline(self, multiline: bool) -> Array:
1185 """Change the array to display in multiline or not.
1187 :Example:
1189 >>> a = item([1, 2, 3])
1190 >>> print(a.as_string())
1191 [1, 2, 3]
1192 >>> print(a.multiline(True).as_string())
1193 [
1194 1,
1195 2,
1196 3,
1197 ]
1198 """
1199 self._multiline = multiline
1201 return self
1203 def as_string(self) -> str:
1204 if not self._multiline or not self._value:
1205 return f'[{"".join(v.as_string() for v in self._iter_items())}]'
1207 s = "[\n"
1208 s += "".join(
1209 self.trivia.indent
1210 + " " * 4
1211 + v.value.as_string()
1212 + ("," if not isinstance(v.value, Null) else "")
1213 + (v.comment.as_string() if v.comment is not None else "")
1214 + "\n"
1215 for v in self._value
1216 if v.value is not None
1217 )
1218 s += self.trivia.indent + "]"
1220 return s
1222 def _reindex(self) -> None:
1223 self._index_map.clear()
1224 index = 0
1225 for i, v in enumerate(self._value):
1226 if v.value is None or isinstance(v.value, Null):
1227 continue
1228 self._index_map[index] = i
1229 index += 1
1231 def add_line(
1232 self,
1233 *items: Any,
1234 indent: str = " ",
1235 comment: str | None = None,
1236 add_comma: bool = True,
1237 newline: bool = True,
1238 ) -> None:
1239 """Add multiple items in a line to control the format precisely.
1240 When add_comma is True, only accept actual values and
1241 ", " will be added between values automatically.
1243 :Example:
1245 >>> a = array()
1246 >>> a.add_line(1, 2, 3)
1247 >>> a.add_line(4, 5, 6)
1248 >>> a.add_line(indent="")
1249 >>> print(a.as_string())
1250 [
1251 1, 2, 3,
1252 4, 5, 6,
1253 ]
1254 """
1255 new_values: list[Item] = []
1256 first_indent = f"\n{indent}" if newline else indent
1257 if first_indent:
1258 new_values.append(Whitespace(first_indent))
1259 whitespace = ""
1260 data_values = []
1261 for i, el in enumerate(items):
1262 it = item(el, _parent=self)
1263 if isinstance(it, Comment) or add_comma and isinstance(el, Whitespace):
1264 raise ValueError(f"item type {type(it)} is not allowed in add_line")
1265 if not isinstance(it, Whitespace):
1266 if whitespace:
1267 new_values.append(Whitespace(whitespace))
1268 whitespace = ""
1269 new_values.append(it)
1270 data_values.append(it.value)
1271 if add_comma:
1272 new_values.append(Whitespace(","))
1273 if i != len(items) - 1:
1274 new_values.append(Whitespace(" "))
1275 elif "," not in it.s:
1276 whitespace += it.s
1277 else:
1278 new_values.append(it)
1279 if whitespace:
1280 new_values.append(Whitespace(whitespace))
1281 if comment:
1282 indent = " " if items else ""
1283 new_values.append(
1284 Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
1285 )
1286 list.extend(self, data_values)
1287 if len(self._value) > 0:
1288 last_item = self._value[-1]
1289 last_value_item = next(
1290 (
1291 v
1292 for v in self._value[::-1]
1293 if v.value is not None and not isinstance(v.value, Null)
1294 ),
1295 None,
1296 )
1297 if last_value_item is not None:
1298 last_value_item.comma = Whitespace(",")
1299 if last_item.is_whitespace():
1300 self._value[-1:-1] = self._group_values(new_values)
1301 else:
1302 self._value.extend(self._group_values(new_values))
1303 else:
1304 self._value.extend(self._group_values(new_values))
1305 self._reindex()
1307 def clear(self) -> None:
1308 """Clear the array."""
1309 list.clear(self)
1310 self._index_map.clear()
1311 self._value.clear()
1313 def __len__(self) -> int:
1314 return list.__len__(self)
1316 def __getitem__(self, key: int | slice) -> Any:
1317 rv = cast(Item, list.__getitem__(self, key))
1318 if rv.is_boolean():
1319 return bool(rv)
1320 return rv
1322 def __setitem__(self, key: int | slice, value: Any) -> Any:
1323 it = item(value, _parent=self)
1324 list.__setitem__(self, key, it)
1325 if isinstance(key, slice):
1326 raise ValueError("slice assignment is not supported")
1327 if key < 0:
1328 key += len(self)
1329 self._value[self._index_map[key]].value = it
1331 def insert(self, pos: int, value: Any) -> None:
1332 it = item(value, _parent=self)
1333 length = len(self)
1334 if not isinstance(it, (Comment, Whitespace)):
1335 list.insert(self, pos, it)
1336 if pos < 0:
1337 pos += length
1338 if pos < 0:
1339 pos = 0
1341 idx = 0 # insert position of the self._value list
1342 default_indent = " "
1343 if pos < length:
1344 try:
1345 idx = self._index_map[pos]
1346 except KeyError as e:
1347 raise IndexError("list index out of range") from e
1348 else:
1349 idx = len(self._value)
1350 if idx >= 1 and self._value[idx - 1].is_whitespace():
1351 # The last item is a pure whitespace(\n ), insert before it
1352 idx -= 1
1353 if (
1354 self._value[idx].indent is not None
1355 and "\n" in self._value[idx].indent.s
1356 ):
1357 default_indent = "\n "
1358 indent: Item | None = None
1359 comma: Item | None = Whitespace(",") if pos < length else None
1360 if idx < len(self._value) and not self._value[idx].is_whitespace():
1361 # Prefer to copy the indentation from the item after
1362 indent = self._value[idx].indent
1363 if idx > 0:
1364 last_item = self._value[idx - 1]
1365 if indent is None:
1366 indent = last_item.indent
1367 if not isinstance(last_item.value, Null) and "\n" in default_indent:
1368 # Copy the comma from the last item if 1) it contains a value and
1369 # 2) the array is multiline
1370 comma = last_item.comma
1371 if last_item.comma is None and not isinstance(last_item.value, Null):
1372 # Add comma to the last item to separate it from the following items.
1373 last_item.comma = Whitespace(",")
1374 if indent is None and (idx > 0 or "\n" in default_indent):
1375 # apply default indent if it isn't the first item or the array is multiline.
1376 indent = Whitespace(default_indent)
1377 new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
1378 self._value.insert(idx, new_item)
1379 self._reindex()
1381 def __delitem__(self, key: int | slice):
1382 length = len(self)
1383 list.__delitem__(self, key)
1385 if isinstance(key, slice):
1386 indices_to_remove = list(
1387 range(key.start or 0, key.stop or length, key.step or 1)
1388 )
1389 else:
1390 indices_to_remove = [length + key if key < 0 else key]
1391 for i in sorted(indices_to_remove, reverse=True):
1392 try:
1393 idx = self._index_map[i]
1394 except KeyError as e:
1395 if not isinstance(key, slice):
1396 raise IndexError("list index out of range") from e
1397 else:
1398 del self._value[idx]
1399 if (
1400 idx == 0
1401 and len(self._value) > 0
1402 and self._value[idx].indent
1403 and "\n" not in self._value[idx].indent.s
1404 ):
1405 # Remove the indentation of the first item if not newline
1406 self._value[idx].indent = None
1407 if len(self._value) > 0:
1408 v = self._value[-1]
1409 if not v.is_whitespace():
1410 # remove the comma of the last item
1411 v.comma = None
1413 self._reindex()
1415 def _getstate(self, protocol=3):
1416 return list(self._iter_items()), self._trivia, self._multiline
1419class AbstractTable(Item, _CustomDict):
1420 """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
1422 def __init__(self, value: container.Container, trivia: Trivia):
1423 Item.__init__(self, trivia)
1425 self._value = value
1427 for k, v in self._value.body:
1428 if k is not None:
1429 dict.__setitem__(self, k.key, v)
1431 def unwrap(self) -> dict[str, Any]:
1432 unwrapped = {}
1433 for k, v in self.items():
1434 if isinstance(k, Key):
1435 k = k.key
1436 if hasattr(v, "unwrap"):
1437 v = v.unwrap()
1438 unwrapped[k] = v
1440 return unwrapped
1442 @property
1443 def value(self) -> container.Container:
1444 return self._value
1446 @overload
1447 def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ...
1449 @overload
1450 def append(self: AT, key: Key | str, value: Any) -> AT: ...
1452 def append(self, key, value):
1453 raise NotImplementedError
1455 @overload
1456 def add(self: AT, key: Comment | Whitespace) -> AT: ...
1458 @overload
1459 def add(self: AT, key: Key | str, value: Any = ...) -> AT: ...
1461 def add(self, key, value=None):
1462 if value is None:
1463 if not isinstance(key, (Comment, Whitespace)):
1464 msg = "Non comment/whitespace items must have an associated key"
1465 raise ValueError(msg)
1467 key, value = None, key
1469 return self.append(key, value)
1471 def remove(self: AT, key: Key | str) -> AT:
1472 self._value.remove(key)
1474 if isinstance(key, Key):
1475 key = key.key
1477 if key is not None:
1478 dict.__delitem__(self, key)
1480 return self
1482 def setdefault(self, key: Key | str, default: Any) -> Any:
1483 super().setdefault(key, default)
1484 return self[key]
1486 def __str__(self):
1487 return str(self.value)
1489 def copy(self: AT) -> AT:
1490 return copy.copy(self)
1492 def __repr__(self) -> str:
1493 return repr(self.value)
1495 def __iter__(self) -> Iterator[str]:
1496 return iter(self._value)
1498 def __len__(self) -> int:
1499 return len(self._value)
1501 def __delitem__(self, key: Key | str) -> None:
1502 self.remove(key)
1504 def __getitem__(self, key: Key | str) -> Item:
1505 return cast(Item, self._value[key])
1507 def __setitem__(self, key: Key | str, value: Any) -> None:
1508 if not isinstance(value, Item):
1509 value = item(value, _parent=self)
1511 is_replace = key in self
1512 self._value[key] = value
1514 if key is not None:
1515 dict.__setitem__(self, key, value)
1517 if is_replace:
1518 return
1519 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1520 if not m:
1521 return
1523 indent = m.group(1)
1525 if not isinstance(value, Whitespace):
1526 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1527 if not m:
1528 value.trivia.indent = indent
1529 else:
1530 value.trivia.indent = m.group(1) + indent + m.group(2)
1533class Table(AbstractTable):
1534 """
1535 A table literal.
1536 """
1538 def __init__(
1539 self,
1540 value: container.Container,
1541 trivia: Trivia,
1542 is_aot_element: bool,
1543 is_super_table: bool | None = None,
1544 name: str | None = None,
1545 display_name: str | None = None,
1546 ) -> None:
1547 super().__init__(value, trivia)
1549 self.name = name
1550 self.display_name = display_name
1551 self._is_aot_element = is_aot_element
1552 self._is_super_table = is_super_table
1554 @property
1555 def discriminant(self) -> int:
1556 return 9
1558 def __copy__(self) -> Table:
1559 return type(self)(
1560 self._value.copy(),
1561 self._trivia.copy(),
1562 self._is_aot_element,
1563 self._is_super_table,
1564 self.name,
1565 self.display_name,
1566 )
1568 def append(self, key: Key | str | None, _item: Any) -> Table:
1569 """
1570 Appends a (key, item) to the table.
1571 """
1572 if not isinstance(_item, Item):
1573 _item = item(_item, _parent=self)
1575 self._value.append(key, _item)
1577 if isinstance(key, Key):
1578 key = next(iter(key)).key
1579 _item = self._value[key]
1581 if key is not None:
1582 dict.__setitem__(self, key, _item)
1584 m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1585 if not m:
1586 return self
1588 indent = m.group(1)
1590 if not isinstance(_item, Whitespace):
1591 m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
1592 if not m:
1593 _item.trivia.indent = indent
1594 else:
1595 _item.trivia.indent = m.group(1) + indent + m.group(2)
1597 return self
1599 def raw_append(self, key: Key | str | None, _item: Any) -> Table:
1600 """Similar to :meth:`append` but does not copy indentation."""
1601 if not isinstance(_item, Item):
1602 _item = item(_item)
1604 self._value.append(key, _item, validate=False)
1606 if isinstance(key, Key):
1607 key = next(iter(key)).key
1608 _item = self._value[key]
1610 if key is not None:
1611 dict.__setitem__(self, key, _item)
1613 return self
1615 def is_aot_element(self) -> bool:
1616 """True if the table is the direct child of an AOT element."""
1617 return self._is_aot_element
1619 def is_super_table(self) -> bool:
1620 """A super table is the intermediate parent of a nested table as in [a.b.c].
1621 If true, it won't appear in the TOML representation."""
1622 if self._is_super_table is not None:
1623 return self._is_super_table
1624 if not self:
1625 return False
1626 # If the table has children and all children are tables, then it is a super table.
1627 for k, child in self.items():
1628 if not isinstance(k, Key):
1629 k = SingleKey(k)
1630 index = self.value._map[k]
1631 if isinstance(index, tuple):
1632 return False
1633 real_key = self.value.body[index][0]
1634 if (
1635 not isinstance(child, (Table, AoT))
1636 or real_key is None
1637 or real_key.is_dotted()
1638 ):
1639 return False
1640 return True
1642 def as_string(self) -> str:
1643 return self._value.as_string()
1645 # Helpers
1647 def indent(self, indent: int) -> Table:
1648 """Indent the table with given number of spaces."""
1649 super().indent(indent)
1651 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1652 if not m:
1653 indent_str = ""
1654 else:
1655 indent_str = m.group(1)
1657 for _, item in self._value.body:
1658 if not isinstance(item, Whitespace):
1659 item.trivia.indent = indent_str + item.trivia.indent
1661 return self
1663 def invalidate_display_name(self):
1664 """Call ``invalidate_display_name`` on the contained tables"""
1665 self.display_name = None
1667 for child in self.values():
1668 if hasattr(child, "invalidate_display_name"):
1669 child.invalidate_display_name()
1671 def _getstate(self, protocol: int = 3) -> tuple:
1672 return (
1673 self._value,
1674 self._trivia,
1675 self._is_aot_element,
1676 self._is_super_table,
1677 self.name,
1678 self.display_name,
1679 )
1682class InlineTable(AbstractTable):
1683 """
1684 An inline table literal.
1685 """
1687 def __init__(
1688 self, value: container.Container, trivia: Trivia, new: bool = False
1689 ) -> None:
1690 super().__init__(value, trivia)
1692 self._new = new
1694 @property
1695 def discriminant(self) -> int:
1696 return 10
1698 def append(self, key: Key | str | None, _item: Any) -> InlineTable:
1699 """
1700 Appends a (key, item) to the table.
1701 """
1702 if not isinstance(_item, Item):
1703 _item = item(_item, _parent=self)
1705 if not isinstance(_item, (Whitespace, Comment)):
1706 if not _item.trivia.indent and len(self._value) > 0 and not self._new:
1707 _item.trivia.indent = " "
1708 if _item.trivia.comment:
1709 _item.trivia.comment = ""
1711 self._value.append(key, _item)
1713 if isinstance(key, Key):
1714 key = key.key
1716 if key is not None:
1717 dict.__setitem__(self, key, _item)
1719 return self
1721 def as_string(self) -> str:
1722 buf = "{"
1723 last_item_idx = next(
1724 (
1725 i
1726 for i in range(len(self._value.body) - 1, -1, -1)
1727 if self._value.body[i][0] is not None
1728 ),
1729 None,
1730 )
1731 for i, (k, v) in enumerate(self._value.body):
1732 if k is None:
1733 if i == len(self._value.body) - 1:
1734 if self._new:
1735 buf = buf.rstrip(", ")
1736 else:
1737 buf = buf.rstrip(",")
1739 buf += v.as_string()
1741 continue
1743 v_trivia_trail = v.trivia.trail.replace("\n", "")
1744 buf += (
1745 f"{v.trivia.indent}"
1746 f'{k.as_string() + ("." if k.is_dotted() else "")}'
1747 f"{k.sep}"
1748 f"{v.as_string()}"
1749 f"{v.trivia.comment}"
1750 f"{v_trivia_trail}"
1751 )
1753 if last_item_idx is not None and i < last_item_idx:
1754 buf += ","
1755 if self._new:
1756 buf += " "
1758 buf += "}"
1760 return buf
1762 def __setitem__(self, key: Key | str, value: Any) -> None:
1763 if hasattr(value, "trivia") and value.trivia.comment:
1764 value.trivia.comment = ""
1765 super().__setitem__(key, value)
1767 def __copy__(self) -> InlineTable:
1768 return type(self)(self._value.copy(), self._trivia.copy(), self._new)
1770 def _getstate(self, protocol: int = 3) -> tuple:
1771 return (self._value, self._trivia)
1774class String(str, Item):
1775 """
1776 A string literal.
1777 """
1779 def __new__(cls, t, value, original, trivia):
1780 return super().__new__(cls, value)
1782 def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
1783 super().__init__(trivia)
1785 self._t = t
1786 self._original = original
1788 def unwrap(self) -> str:
1789 return str(self)
1791 @property
1792 def discriminant(self) -> int:
1793 return 11
1795 @property
1796 def value(self) -> str:
1797 return self
1799 def as_string(self) -> str:
1800 return f"{self._t.value}{decode(self._original)}{self._t.value}"
1802 def __add__(self: ItemT, other: str) -> ItemT:
1803 if not isinstance(other, str):
1804 return NotImplemented
1805 result = super().__add__(other)
1806 original = self._original + getattr(other, "_original", other)
1808 return self._new(result, original)
1810 def _new(self, result: str, original: str) -> String:
1811 return String(self._t, result, original, self._trivia)
1813 def _getstate(self, protocol=3):
1814 return self._t, str(self), self._original, self._trivia
1816 @classmethod
1817 def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String:
1818 value = decode(value)
1820 invalid = type_.invalid_sequences
1821 if any(c in value for c in invalid):
1822 raise InvalidStringError(value, invalid, type_.value)
1824 escaped = type_.escaped_sequences
1825 string_value = escape_string(value, escaped) if escape and escaped else value
1827 return cls(type_, decode(value), string_value, Trivia())
1830class AoT(Item, _CustomList):
1831 """
1832 An array of table literal
1833 """
1835 def __init__(
1836 self, body: list[Table], name: str | None = None, parsed: bool = False
1837 ) -> None:
1838 self.name = name
1839 self._body: list[Table] = []
1840 self._parsed = parsed
1842 super().__init__(Trivia(trail=""))
1844 for table in body:
1845 self.append(table)
1847 def unwrap(self) -> list[dict[str, Any]]:
1848 unwrapped = []
1849 for t in self._body:
1850 if hasattr(t, "unwrap"):
1851 unwrapped.append(t.unwrap())
1852 else:
1853 unwrapped.append(t)
1854 return unwrapped
1856 @property
1857 def body(self) -> list[Table]:
1858 return self._body
1860 @property
1861 def discriminant(self) -> int:
1862 return 12
1864 @property
1865 def value(self) -> list[dict[Any, Any]]:
1866 return [v.value for v in self._body]
1868 def __len__(self) -> int:
1869 return len(self._body)
1871 @overload
1872 def __getitem__(self, key: slice) -> list[Table]: ...
1874 @overload
1875 def __getitem__(self, key: int) -> Table: ...
1877 def __getitem__(self, key):
1878 return self._body[key]
1880 def __setitem__(self, key: slice | int, value: Any) -> None:
1881 raise NotImplementedError
1883 def __delitem__(self, key: slice | int) -> None:
1884 del self._body[key]
1885 list.__delitem__(self, key)
1887 def insert(self, index: int, value: dict) -> None:
1888 value = item(value, _parent=self)
1889 if not isinstance(value, Table):
1890 raise ValueError(f"Unsupported insert value type: {type(value)}")
1891 length = len(self)
1892 if index < 0:
1893 index += length
1894 if index < 0:
1895 index = 0
1896 elif index >= length:
1897 index = length
1898 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1899 if m:
1900 indent = m.group(1)
1902 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1903 if not m:
1904 value.trivia.indent = indent
1905 else:
1906 value.trivia.indent = m.group(1) + indent + m.group(2)
1907 prev_table = self._body[index - 1] if 0 < index and length else None
1908 next_table = self._body[index + 1] if index < length - 1 else None
1909 if not self._parsed:
1910 if prev_table and "\n" not in value.trivia.indent:
1911 value.trivia.indent = "\n" + value.trivia.indent
1912 if next_table and "\n" not in next_table.trivia.indent:
1913 next_table.trivia.indent = "\n" + next_table.trivia.indent
1914 self._body.insert(index, value)
1915 list.insert(self, index, value)
1917 def invalidate_display_name(self):
1918 """Call ``invalidate_display_name`` on the contained tables"""
1919 for child in self:
1920 if hasattr(child, "invalidate_display_name"):
1921 child.invalidate_display_name()
1923 def as_string(self) -> str:
1924 b = ""
1925 for table in self._body:
1926 b += table.as_string()
1928 return b
1930 def __repr__(self) -> str:
1931 return f"<AoT {self.value}>"
1933 def _getstate(self, protocol=3):
1934 return self._body, self.name, self._parsed
1937class Null(Item):
1938 """
1939 A null item.
1940 """
1942 def __init__(self) -> None:
1943 super().__init__(Trivia(trail=""))
1945 def unwrap(self) -> None:
1946 return None
1948 @property
1949 def discriminant(self) -> int:
1950 return -1
1952 @property
1953 def value(self) -> None:
1954 return None
1956 def as_string(self) -> str:
1957 return ""
1959 def _getstate(self, protocol=3) -> tuple:
1960 return ()