Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tomlkit/items.py: 57%
1051 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 07:01 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 07:01 +0000
1import abc
2import copy
3import re
4import string
6from datetime import date
7from datetime import datetime
8from datetime import time
9from datetime import tzinfo
10from enum import Enum
11from typing import TYPE_CHECKING
12from typing import Any
13from typing import Collection
14from typing import Dict
15from typing import Iterable
16from typing import Iterator
17from typing import List
18from typing import Optional
19from typing import Sequence
20from typing import TypeVar
21from typing import Union
22from typing import cast
23from typing import overload
25from tomlkit._compat import PY38
26from tomlkit._compat import decode
27from tomlkit._utils import CONTROL_CHARS
28from tomlkit._utils import escape_string
29from tomlkit.exceptions import InvalidStringError
32if TYPE_CHECKING: # pragma: no cover
33 # Define _CustomList and _CustomDict as a workaround for:
34 # https://github.com/python/mypy/issues/11427
35 #
36 # According to this issue, the typeshed contains a "lie"
37 # (it adds MutableSequence to the ancestry of list and MutableMapping to
38 # the ancestry of dict) which completely messes with the type inference for
39 # Table, InlineTable, Array and Container.
40 #
41 # Importing from builtins is preferred over simple assignment, see issues:
42 # https://github.com/python/mypy/issues/8715
43 # https://github.com/python/mypy/issues/10068
44 from builtins import dict as _CustomDict # noqa: N812, TC004
45 from builtins import list as _CustomList # noqa: N812, TC004
47 # Allow type annotations but break circular imports
48 from tomlkit import container
49else:
50 from collections.abc import MutableMapping
51 from collections.abc import MutableSequence
53 class _CustomList(MutableSequence, list):
54 """Adds MutableSequence mixin while pretending to be a builtin list"""
56 class _CustomDict(MutableMapping, dict):
57 """Adds MutableMapping mixin while pretending to be a builtin dict"""
60ItemT = TypeVar("ItemT", bound="Item")
63@overload
64def item(
65 value: bool, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
66) -> "Bool":
67 ...
70@overload
71def item(
72 value: int, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
73) -> "Integer":
74 ...
77@overload
78def item(
79 value: float, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
80) -> "Float":
81 ...
84@overload
85def item(
86 value: str, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
87) -> "String":
88 ...
91@overload
92def item(
93 value: datetime, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
94) -> "DateTime":
95 ...
98@overload
99def item(
100 value: date, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
101) -> "Date":
102 ...
105@overload
106def item(
107 value: time, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
108) -> "Time":
109 ...
112@overload
113def item(
114 value: Sequence[dict], _parent: Optional["Item"] = ..., _sort_keys: bool = ...
115) -> "AoT":
116 ...
119@overload
120def item(
121 value: Sequence, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
122) -> "Array":
123 ...
126@overload
127def item(value: dict, _parent: "Array" = ..., _sort_keys: bool = ...) -> "InlineTable":
128 ...
131@overload
132def item(
133 value: dict, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
134) -> "Table":
135 ...
138@overload
139def item(
140 value: ItemT, _parent: Optional["Item"] = ..., _sort_keys: bool = ...
141) -> ItemT:
142 ...
145def item(
146 value: Any, _parent: Optional["Item"] = None, _sort_keys: bool = False
147) -> "Item":
148 """Create a TOML item from a Python object.
150 :Example:
152 >>> item(42)
153 42
154 >>> item([1, 2, 3])
155 [1, 2, 3]
156 >>> item({'a': 1, 'b': 2})
157 a = 1
158 b = 2
159 """
161 from tomlkit.container import Container
163 if isinstance(value, Item):
164 return value
166 if isinstance(value, bool):
167 return Bool(value, Trivia())
168 elif isinstance(value, int):
169 return Integer(value, Trivia(), str(value))
170 elif isinstance(value, float):
171 return Float(value, Trivia(), str(value))
172 elif isinstance(value, dict):
173 table_constructor = (
174 InlineTable if isinstance(_parent, (Array, InlineTable)) else Table
175 )
176 val = table_constructor(Container(), Trivia(), False)
177 for k, v in sorted(
178 value.items(),
179 key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
180 ):
181 val[k] = item(v, _parent=val, _sort_keys=_sort_keys)
183 return val
184 elif isinstance(value, (list, tuple)):
185 if (
186 value
187 and all(isinstance(v, dict) for v in value)
188 and (_parent is None or isinstance(_parent, Table))
189 ):
190 a = AoT([])
191 table_constructor = Table
192 else:
193 a = Array([], Trivia())
194 table_constructor = InlineTable
196 for v in value:
197 if isinstance(v, dict):
198 table = table_constructor(Container(), Trivia(), True)
200 for k, _v in sorted(
201 v.items(),
202 key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
203 ):
204 i = item(_v, _parent=table, _sort_keys=_sort_keys)
205 if isinstance(table, InlineTable):
206 i.trivia.trail = ""
208 table[k] = i
210 v = table
212 a.append(v)
214 return a
215 elif isinstance(value, str):
216 return String.from_raw(value)
217 elif isinstance(value, datetime):
218 return DateTime(
219 value.year,
220 value.month,
221 value.day,
222 value.hour,
223 value.minute,
224 value.second,
225 value.microsecond,
226 value.tzinfo,
227 Trivia(),
228 value.isoformat().replace("+00:00", "Z"),
229 )
230 elif isinstance(value, date):
231 return Date(value.year, value.month, value.day, Trivia(), value.isoformat())
232 elif isinstance(value, time):
233 return Time(
234 value.hour,
235 value.minute,
236 value.second,
237 value.microsecond,
238 value.tzinfo,
239 Trivia(),
240 value.isoformat(),
241 )
243 raise ValueError(f"Invalid type {type(value)}")
246class StringType(Enum):
247 # Single Line Basic
248 SLB = '"'
249 # Multi Line Basic
250 MLB = '"""'
251 # Single Line Literal
252 SLL = "'"
253 # Multi Line Literal
254 MLL = "'''"
256 @classmethod
257 def select(cls, literal=False, multiline=False) -> "StringType":
258 return {
259 (False, False): cls.SLB,
260 (False, True): cls.MLB,
261 (True, False): cls.SLL,
262 (True, True): cls.MLL,
263 }[(literal, multiline)]
265 @property
266 def escaped_sequences(self) -> Collection[str]:
267 # https://toml.io/en/v1.0.0#string
268 escaped_in_basic = CONTROL_CHARS | {"\\"}
269 allowed_in_multiline = {"\n", "\r"}
270 return {
271 StringType.SLB: escaped_in_basic | {'"'},
272 StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline,
273 StringType.SLL: (),
274 StringType.MLL: (),
275 }[self]
277 @property
278 def invalid_sequences(self) -> Collection[str]:
279 # https://toml.io/en/v1.0.0#string
280 forbidden_in_literal = CONTROL_CHARS - {"\t"}
281 allowed_in_multiline = {"\n", "\r"}
282 return {
283 StringType.SLB: (),
284 StringType.MLB: (),
285 StringType.SLL: forbidden_in_literal | {"'"},
286 StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline,
287 }[self]
289 @property
290 def unit(self) -> str:
291 return self.value[0]
293 def is_basic(self) -> bool:
294 return self in {StringType.SLB, StringType.MLB}
296 def is_literal(self) -> bool:
297 return self in {StringType.SLL, StringType.MLL}
299 def is_singleline(self) -> bool:
300 return self in {StringType.SLB, StringType.SLL}
302 def is_multiline(self) -> bool:
303 return self in {StringType.MLB, StringType.MLL}
305 def toggle(self) -> "StringType":
306 return {
307 StringType.SLB: StringType.MLB,
308 StringType.MLB: StringType.SLB,
309 StringType.SLL: StringType.MLL,
310 StringType.MLL: StringType.SLL,
311 }[self]
314class BoolType(Enum):
315 TRUE = "true"
316 FALSE = "false"
318 def __bool__(self):
319 return {BoolType.TRUE: True, BoolType.FALSE: False}[self]
321 def __iter__(self):
322 return iter(self.value)
324 def __len__(self):
325 return len(self.value)
328class Trivia:
329 """
330 Trivia information (aka metadata).
331 """
333 def __init__(
334 self,
335 indent: str = None,
336 comment_ws: str = None,
337 comment: str = None,
338 trail: str = None,
339 ) -> None:
340 # Whitespace before a value.
341 self.indent = indent or ""
342 # Whitespace after a value, but before a comment.
343 self.comment_ws = comment_ws or ""
344 # Comment, starting with # character, or empty string if no comment.
345 self.comment = comment or ""
346 # Trailing newline.
347 if trail is None:
348 trail = "\n"
350 self.trail = trail
352 def copy(self) -> "Trivia":
353 return type(self)(self.indent, self.comment_ws, self.comment, self.trail)
356class KeyType(Enum):
357 """
358 The type of a Key.
360 Keys can be bare (unquoted), or quoted using basic ("), or literal (')
361 quotes following the same escaping rules as single-line StringType.
362 """
364 Bare = ""
365 Basic = '"'
366 Literal = "'"
369class Key(abc.ABC):
370 """Base class for a key"""
372 sep: str
373 _original: str
374 _keys: List["SingleKey"]
375 _dotted: bool
376 key: str
378 @abc.abstractmethod
379 def __hash__(self) -> int:
380 pass
382 @abc.abstractmethod
383 def __eq__(self, __o: object) -> bool:
384 pass
386 def is_dotted(self) -> bool:
387 """If the key is followed by other keys"""
388 return self._dotted
390 def __iter__(self) -> Iterator["SingleKey"]:
391 return iter(self._keys)
393 def concat(self, other: "Key") -> "DottedKey":
394 """Concatenate keys into a dotted key"""
395 keys = self._keys + other._keys
396 return DottedKey(keys, sep=self.sep)
398 def is_multi(self) -> bool:
399 """Check if the key contains multiple keys"""
400 return len(self._keys) > 1
402 def as_string(self) -> str:
403 """The TOML representation"""
404 return self._original
406 def __str__(self) -> str:
407 return self.as_string()
409 def __repr__(self) -> str:
410 return f"<Key {self.as_string()}>"
413class SingleKey(Key):
414 """A single key"""
416 def __init__(
417 self,
418 k: str,
419 t: Optional[KeyType] = None,
420 sep: Optional[str] = None,
421 original: Optional[str] = None,
422 ) -> None:
423 if t is None:
424 if not k or any(
425 c not in string.ascii_letters + string.digits + "-" + "_" for c in k
426 ):
427 t = KeyType.Basic
428 else:
429 t = KeyType.Bare
431 self.t = t
432 if sep is None:
433 sep = " = "
435 self.sep = sep
436 self.key = k
437 if original is None:
438 key_str = escape_string(k) if t == KeyType.Basic else k
439 original = f"{t.value}{key_str}{t.value}"
441 self._original = original
442 self._keys = [self]
443 self._dotted = False
445 @property
446 def delimiter(self) -> str:
447 """The delimiter: double quote/single quote/none"""
448 return self.t.value
450 def is_bare(self) -> bool:
451 """Check if the key is bare"""
452 return self.t == KeyType.Bare
454 def __hash__(self) -> int:
455 return hash(self.key)
457 def __eq__(self, other: Any) -> bool:
458 if isinstance(other, Key):
459 return isinstance(other, SingleKey) and self.key == other.key
461 return self.key == other
464class DottedKey(Key):
465 def __init__(
466 self,
467 keys: Iterable[Key],
468 sep: Optional[str] = None,
469 original: Optional[str] = None,
470 ) -> None:
471 self._keys = list(keys)
472 if original is None:
473 original = ".".join(k.as_string() for k in self._keys)
475 self.sep = " = " if sep is None else sep
476 self._original = original
477 self._dotted = False
478 self.key = ".".join(k.key for k in self._keys)
480 def __hash__(self) -> int:
481 return hash(tuple(self._keys))
483 def __eq__(self, __o: object) -> bool:
484 return isinstance(__o, DottedKey) and self._keys == __o._keys
487class Item:
488 """
489 An item within a TOML document.
490 """
492 def __init__(self, trivia: Trivia) -> None:
493 self._trivia = trivia
495 @property
496 def trivia(self) -> Trivia:
497 """The trivia element associated with this item"""
498 return self._trivia
500 @property
501 def discriminant(self) -> int:
502 raise NotImplementedError()
504 def as_string(self) -> str:
505 """The TOML representation"""
506 raise NotImplementedError()
508 @property
509 def value(self) -> Any:
510 return self
512 def unwrap(self) -> Any:
513 """Returns as pure python object (ppo)"""
514 raise NotImplementedError()
516 # Helpers
518 def comment(self, comment: str) -> "Item":
519 """Attach a comment to this item"""
520 if not comment.strip().startswith("#"):
521 comment = "# " + comment
523 self._trivia.comment_ws = " "
524 self._trivia.comment = comment
526 return self
528 def indent(self, indent: int) -> "Item":
529 """Indent this item with given number of spaces"""
530 if self._trivia.indent.startswith("\n"):
531 self._trivia.indent = "\n" + " " * indent
532 else:
533 self._trivia.indent = " " * indent
535 return self
537 def is_boolean(self) -> bool:
538 return isinstance(self, Bool)
540 def is_table(self) -> bool:
541 return isinstance(self, Table)
543 def is_inline_table(self) -> bool:
544 return isinstance(self, InlineTable)
546 def is_aot(self) -> bool:
547 return isinstance(self, AoT)
549 def _getstate(self, protocol=3):
550 return (self._trivia,)
552 def __reduce__(self):
553 return self.__reduce_ex__(2)
555 def __reduce_ex__(self, protocol):
556 return self.__class__, self._getstate(protocol)
559class Whitespace(Item):
560 """
561 A whitespace literal.
562 """
564 def __init__(self, s: str, fixed: bool = False) -> None:
565 self._s = s
566 self._fixed = fixed
568 @property
569 def s(self) -> str:
570 return self._s
572 @property
573 def value(self) -> str:
574 """The wrapped string of the whitespace"""
575 return self._s
577 @property
578 def trivia(self) -> Trivia:
579 raise RuntimeError("Called trivia on a Whitespace variant.")
581 @property
582 def discriminant(self) -> int:
583 return 0
585 def is_fixed(self) -> bool:
586 """If the whitespace is fixed, it can't be merged or discarded from the output."""
587 return self._fixed
589 def as_string(self) -> str:
590 return self._s
592 def __repr__(self) -> str:
593 return f"<{self.__class__.__name__} {repr(self._s)}>"
595 def _getstate(self, protocol=3):
596 return self._s, self._fixed
599class Comment(Item):
600 """
601 A comment literal.
602 """
604 @property
605 def discriminant(self) -> int:
606 return 1
608 def as_string(self) -> str:
609 return (
610 f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
611 )
613 def __str__(self) -> str:
614 return f"{self._trivia.indent}{decode(self._trivia.comment)}"
617class Integer(int, Item):
618 """
619 An integer literal.
620 """
622 def __new__(cls, value: int, trivia: Trivia, raw: str) -> "Integer":
623 return super().__new__(cls, value)
625 def __init__(self, _: int, trivia: Trivia, raw: str) -> None:
626 super().__init__(trivia)
628 self._raw = raw
629 self._sign = False
631 if re.match(r"^[+\-]\d+$", raw):
632 self._sign = True
634 def unwrap(self) -> int:
635 return int(self)
637 @property
638 def discriminant(self) -> int:
639 return 2
641 @property
642 def value(self) -> int:
643 """The wrapped integer value"""
644 return self
646 def as_string(self) -> str:
647 return self._raw
649 def __add__(self, other):
650 return self._new(int(self._raw) + other)
652 def __radd__(self, other):
653 result = super().__radd__(other)
655 if isinstance(other, Integer):
656 return self._new(result)
658 return result
660 def __sub__(self, other):
661 result = super().__sub__(other)
663 return self._new(result)
665 def __rsub__(self, other):
666 result = super().__rsub__(other)
668 if isinstance(other, Integer):
669 return self._new(result)
671 return result
673 def _new(self, result):
674 raw = str(result)
675 if self._sign:
676 sign = "+" if result >= 0 else "-"
677 raw = sign + raw
679 return Integer(result, self._trivia, raw)
681 def _getstate(self, protocol=3):
682 return int(self), self._trivia, self._raw
685class Float(float, Item):
686 """
687 A float literal.
688 """
690 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Integer:
691 return super().__new__(cls, value)
693 def __init__(self, _: float, trivia: Trivia, raw: str) -> None:
694 super().__init__(trivia)
696 self._raw = raw
697 self._sign = False
699 if re.match(r"^[+\-].+$", raw):
700 self._sign = True
702 def unwrap(self) -> float:
703 return float(self)
705 @property
706 def discriminant(self) -> int:
707 return 3
709 @property
710 def value(self) -> float:
711 """The wrapped float value"""
712 return self
714 def as_string(self) -> str:
715 return self._raw
717 def __add__(self, other):
718 result = super().__add__(other)
720 return self._new(result)
722 def __radd__(self, other):
723 result = super().__radd__(other)
725 if isinstance(other, Float):
726 return self._new(result)
728 return result
730 def __sub__(self, other):
731 result = super().__sub__(other)
733 return self._new(result)
735 def __rsub__(self, other):
736 result = super().__rsub__(other)
738 if isinstance(other, Float):
739 return self._new(result)
741 return result
743 def _new(self, result):
744 raw = str(result)
746 if self._sign:
747 sign = "+" if result >= 0 else "-"
748 raw = sign + raw
750 return Float(result, self._trivia, raw)
752 def _getstate(self, protocol=3):
753 return float(self), self._trivia, self._raw
756class Bool(Item):
757 """
758 A boolean literal.
759 """
761 def __init__(self, t: int, trivia: Trivia) -> None:
762 super().__init__(trivia)
764 self._value = bool(t)
766 def unwrap(self) -> bool:
767 return bool(self)
769 @property
770 def discriminant(self) -> int:
771 return 4
773 @property
774 def value(self) -> bool:
775 """The wrapped boolean value"""
776 return self._value
778 def as_string(self) -> str:
779 return str(self._value).lower()
781 def _getstate(self, protocol=3):
782 return self._value, self._trivia
784 def __bool__(self):
785 return self._value
787 __nonzero__ = __bool__
789 def __eq__(self, other):
790 if not isinstance(other, bool):
791 return NotImplemented
793 return other == self._value
795 def __hash__(self):
796 return hash(self._value)
798 def __repr__(self):
799 return repr(self._value)
802class DateTime(Item, datetime):
803 """
804 A datetime literal.
805 """
807 def __new__(
808 cls,
809 year: int,
810 month: int,
811 day: int,
812 hour: int,
813 minute: int,
814 second: int,
815 microsecond: int,
816 tzinfo: Optional[tzinfo],
817 *_: Any,
818 **kwargs: Any,
819 ) -> datetime:
820 return datetime.__new__(
821 cls,
822 year,
823 month,
824 day,
825 hour,
826 minute,
827 second,
828 microsecond,
829 tzinfo=tzinfo,
830 **kwargs,
831 )
833 def __init__(
834 self,
835 year: int,
836 month: int,
837 day: int,
838 hour: int,
839 minute: int,
840 second: int,
841 microsecond: int,
842 tzinfo: Optional[tzinfo],
843 trivia: Optional[Trivia] = None,
844 raw: Optional[str] = None,
845 **kwargs: Any,
846 ) -> None:
847 super().__init__(trivia or Trivia())
849 self._raw = raw or self.isoformat()
851 def unwrap(self) -> datetime:
852 (
853 year,
854 month,
855 day,
856 hour,
857 minute,
858 second,
859 microsecond,
860 tzinfo,
861 _,
862 _,
863 ) = self._getstate()
864 return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
866 @property
867 def discriminant(self) -> int:
868 return 5
870 @property
871 def value(self) -> datetime:
872 return self
874 def as_string(self) -> str:
875 return self._raw
877 def __add__(self, other):
878 if PY38:
879 result = datetime(
880 self.year,
881 self.month,
882 self.day,
883 self.hour,
884 self.minute,
885 self.second,
886 self.microsecond,
887 self.tzinfo,
888 ).__add__(other)
889 else:
890 result = super().__add__(other)
892 return self._new(result)
894 def __sub__(self, other):
895 if PY38:
896 result = datetime(
897 self.year,
898 self.month,
899 self.day,
900 self.hour,
901 self.minute,
902 self.second,
903 self.microsecond,
904 self.tzinfo,
905 ).__sub__(other)
906 else:
907 result = super().__sub__(other)
909 if isinstance(result, datetime):
910 result = self._new(result)
912 return result
914 def replace(self, *args: Any, **kwargs: Any) -> datetime:
915 return self._new(super().replace(*args, **kwargs))
917 def astimezone(self, tz: tzinfo) -> datetime:
918 result = super().astimezone(tz)
919 if PY38:
920 return result
921 return self._new(result)
923 def _new(self, result) -> "DateTime":
924 raw = result.isoformat()
926 return DateTime(
927 result.year,
928 result.month,
929 result.day,
930 result.hour,
931 result.minute,
932 result.second,
933 result.microsecond,
934 result.tzinfo,
935 self._trivia,
936 raw,
937 )
939 def _getstate(self, protocol=3):
940 return (
941 self.year,
942 self.month,
943 self.day,
944 self.hour,
945 self.minute,
946 self.second,
947 self.microsecond,
948 self.tzinfo,
949 self._trivia,
950 self._raw,
951 )
954class Date(Item, date):
955 """
956 A date literal.
957 """
959 def __new__(cls, year: int, month: int, day: int, *_: Any) -> date:
960 return date.__new__(cls, year, month, day)
962 def __init__(
963 self, year: int, month: int, day: int, trivia: Trivia, raw: str
964 ) -> None:
965 super().__init__(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: Optional[tzinfo],
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: Optional[tzinfo],
1038 trivia: Trivia,
1039 raw: str,
1040 ) -> None:
1041 super().__init__(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__ = ("value", "indent", "comma", "comment")
1091 def __init__(
1092 self,
1093 value: Optional[Item] = None,
1094 indent: Optional[Whitespace] = None,
1095 comma: Optional[Whitespace] = None,
1096 comment: Optional[Comment] = 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 for item in value:
1145 if isinstance(item, Whitespace):
1146 if "," not in item.s:
1147 groups.append(this_group)
1148 this_group = _ArrayItemGroup(indent=item)
1149 else:
1150 if this_group.value is None:
1151 # when comma is met and no value is provided, add a dummy Null
1152 this_group.value = Null()
1153 this_group.comma = item
1154 elif isinstance(item, Comment):
1155 if this_group.value is None:
1156 this_group.value = Null()
1157 this_group.comment = item
1158 elif this_group.value is None:
1159 this_group.value = item
1160 else:
1161 groups.append(this_group)
1162 this_group = _ArrayItemGroup(value=item)
1163 groups.append(this_group)
1164 return [group for group in groups if group]
1166 def unwrap(self) -> List[Any]:
1167 unwrapped = []
1168 for v in self:
1169 if hasattr(v, "unwrap"):
1170 unwrapped.append(v.unwrap())
1171 else:
1172 unwrapped.append(v)
1173 return unwrapped
1175 @property
1176 def discriminant(self) -> int:
1177 return 8
1179 @property
1180 def value(self) -> list:
1181 return self
1183 def _iter_items(self) -> Iterator[Item]:
1184 for v in self._value:
1185 yield from v
1187 def multiline(self, multiline: bool) -> "Array":
1188 """Change the array to display in multiline or not.
1190 :Example:
1192 >>> a = item([1, 2, 3])
1193 >>> print(a.as_string())
1194 [1, 2, 3]
1195 >>> print(a.multiline(True).as_string())
1196 [
1197 1,
1198 2,
1199 3,
1200 ]
1201 """
1202 self._multiline = multiline
1204 return self
1206 def as_string(self) -> str:
1207 if not self._multiline or not self._value:
1208 return f'[{"".join(v.as_string() for v in self._iter_items())}]'
1210 s = "[\n"
1211 s += "".join(
1212 self.trivia.indent
1213 + " " * 4
1214 + v.value.as_string()
1215 + ("," if not isinstance(v.value, Null) else "")
1216 + (v.comment.as_string() if v.comment is not None else "")
1217 + "\n"
1218 for v in self._value
1219 if v.value is not None
1220 )
1221 s += self.trivia.indent + "]"
1223 return s
1225 def _reindex(self) -> None:
1226 self._index_map.clear()
1227 index = 0
1228 for i, v in enumerate(self._value):
1229 if v.value is None or isinstance(v.value, Null):
1230 continue
1231 self._index_map[index] = i
1232 index += 1
1234 def add_line(
1235 self,
1236 *items: Any,
1237 indent: str = " ",
1238 comment: Optional[str] = None,
1239 add_comma: bool = True,
1240 newline: bool = True,
1241 ) -> None:
1242 """Add multiple items in a line to control the format precisely.
1243 When add_comma is True, only accept actual values and
1244 ", " will be added between values automatically.
1246 :Example:
1248 >>> a = array()
1249 >>> a.add_line(1, 2, 3)
1250 >>> a.add_line(4, 5, 6)
1251 >>> a.add_line(indent="")
1252 >>> print(a.as_string())
1253 [
1254 1, 2, 3,
1255 4, 5, 6,
1256 ]
1257 """
1258 new_values: List[Item] = []
1259 first_indent = f"\n{indent}" if newline else indent
1260 if first_indent:
1261 new_values.append(Whitespace(first_indent))
1262 whitespace = ""
1263 data_values = []
1264 for i, el in enumerate(items):
1265 it = item(el, _parent=self)
1266 if isinstance(it, Comment) or add_comma and isinstance(el, Whitespace):
1267 raise ValueError(f"item type {type(it)} is not allowed in add_line")
1268 if not isinstance(it, Whitespace):
1269 if whitespace:
1270 new_values.append(Whitespace(whitespace))
1271 whitespace = ""
1272 new_values.append(it)
1273 data_values.append(it.value)
1274 if add_comma:
1275 new_values.append(Whitespace(","))
1276 if i != len(items) - 1:
1277 new_values.append(Whitespace(" "))
1278 elif "," not in it.s:
1279 whitespace += it.s
1280 else:
1281 new_values.append(it)
1282 if whitespace:
1283 new_values.append(Whitespace(whitespace))
1284 if comment:
1285 indent = " " if items else ""
1286 new_values.append(
1287 Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
1288 )
1289 list.extend(self, data_values)
1290 if len(self._value) > 0:
1291 last_item = self._value[-1]
1292 last_value_item = next(
1293 (
1294 v
1295 for v in self._value[::-1]
1296 if v.value is not None and not isinstance(v.value, Null)
1297 ),
1298 None,
1299 )
1300 if last_value_item is not None:
1301 last_value_item.comma = Whitespace(",")
1302 if last_item.is_whitespace():
1303 self._value[-1:-1] = self._group_values(new_values)
1304 else:
1305 self._value.extend(self._group_values(new_values))
1306 else:
1307 self._value.extend(self._group_values(new_values))
1308 self._reindex()
1310 def clear(self) -> None:
1311 """Clear the array."""
1312 list.clear(self)
1313 self._index_map.clear()
1314 self._value.clear()
1316 def __len__(self) -> int:
1317 return list.__len__(self)
1319 def __getitem__(self, key: Union[int, slice]) -> Any:
1320 rv = cast(Item, list.__getitem__(self, key))
1321 if rv.is_boolean():
1322 return bool(rv)
1323 return rv
1325 def __setitem__(self, key: Union[int, slice], value: Any) -> Any:
1326 it = item(value, _parent=self)
1327 list.__setitem__(self, key, it)
1328 if isinstance(key, slice):
1329 raise ValueError("slice assignment is not supported")
1330 if key < 0:
1331 key += len(self)
1332 self._value[self._index_map[key]].value = it
1334 def insert(self, pos: int, value: Any) -> None:
1335 it = item(value, _parent=self)
1336 length = len(self)
1337 if not isinstance(it, (Comment, Whitespace)):
1338 list.insert(self, pos, it)
1339 if pos < 0:
1340 pos += length
1341 if pos < 0:
1342 pos = 0
1344 idx = 0 # insert position of the self._value list
1345 default_indent = " "
1346 if pos < length:
1347 try:
1348 idx = self._index_map[pos]
1349 except KeyError as e:
1350 raise IndexError("list index out of range") from e
1351 else:
1352 idx = len(self._value)
1353 if idx >= 1 and self._value[idx - 1].is_whitespace():
1354 # The last item is a pure whitespace(\n ), insert before it
1355 idx -= 1
1356 if (
1357 self._value[idx].indent is not None
1358 and "\n" in self._value[idx].indent.s
1359 ):
1360 default_indent = "\n "
1361 indent: Optional[Item] = None
1362 comma: Optional[Item] = Whitespace(",") if pos < length else None
1363 if idx < len(self._value) and not self._value[idx].is_whitespace():
1364 # Prefer to copy the indentation from the item after
1365 indent = self._value[idx].indent
1366 if idx > 0:
1367 last_item = self._value[idx - 1]
1368 if indent is None:
1369 indent = last_item.indent
1370 if not isinstance(last_item.value, Null) and "\n" in default_indent:
1371 # Copy the comma from the last item if 1) it contains a value and
1372 # 2) the array is multiline
1373 comma = last_item.comma
1374 if last_item.comma is None and not isinstance(last_item.value, Null):
1375 # Add comma to the last item to separate it from the following items.
1376 last_item.comma = Whitespace(",")
1377 if indent is None and (idx > 0 or "\n" in default_indent):
1378 # apply default indent if it isn't the first item or the array is multiline.
1379 indent = Whitespace(default_indent)
1380 new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
1381 self._value.insert(idx, new_item)
1382 self._reindex()
1384 def __delitem__(self, key: Union[int, slice]):
1385 length = len(self)
1386 list.__delitem__(self, key)
1388 if isinstance(key, slice):
1389 indices_to_remove = list(
1390 range(key.start or 0, key.stop or length, key.step or 1)
1391 )
1392 else:
1393 indices_to_remove = [length + key if key < 0 else key]
1394 for i in sorted(indices_to_remove, reverse=True):
1395 try:
1396 idx = self._index_map[i]
1397 except KeyError as e:
1398 if not isinstance(key, slice):
1399 raise IndexError("list index out of range") from e
1400 else:
1401 del self._value[idx]
1402 if (
1403 idx == 0
1404 and len(self._value) > 0
1405 and "\n" not in self._value[idx].indent.s
1406 ):
1407 # Remove the indentation of the first item if not newline
1408 self._value[idx].indent = None
1409 if len(self._value) > 0:
1410 v = self._value[-1]
1411 if not v.is_whitespace():
1412 # remove the comma of the last item
1413 v.comma = None
1415 self._reindex()
1417 def __str__(self):
1418 return str([v.value.value for v in self._iter_items() if v.value is not None])
1420 def _getstate(self, protocol=3):
1421 return list(self._iter_items()), self._trivia, self._multiline
1424AT = TypeVar("AT", bound="AbstractTable")
1427class AbstractTable(Item, _CustomDict):
1428 """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
1430 def __init__(self, value: "container.Container", trivia: Trivia):
1431 Item.__init__(self, trivia)
1433 self._value = value
1435 for k, v in self._value.body:
1436 if k is not None:
1437 dict.__setitem__(self, k.key, v)
1439 def unwrap(self) -> Dict[str, Any]:
1440 unwrapped = {}
1441 for k, v in self.items():
1442 if isinstance(k, Key):
1443 k = k.key
1444 if hasattr(v, "unwrap"):
1445 v = v.unwrap()
1446 unwrapped[k] = v
1448 return unwrapped
1450 @property
1451 def value(self) -> "container.Container":
1452 return self._value
1454 @overload
1455 def append(self: AT, key: None, value: Union[Comment, Whitespace]) -> AT:
1456 ...
1458 @overload
1459 def append(self: AT, key: Union[Key, str], value: Any) -> AT:
1460 ...
1462 def append(self, key, value):
1463 raise NotImplementedError
1465 @overload
1466 def add(self: AT, value: Union[Comment, Whitespace]) -> AT:
1467 ...
1469 @overload
1470 def add(self: AT, key: Union[Key, str], value: Any) -> AT:
1471 ...
1473 def add(self, key, value=None):
1474 if value is None:
1475 if not isinstance(key, (Comment, Whitespace)):
1476 msg = "Non comment/whitespace items must have an associated key"
1477 raise ValueError(msg)
1479 key, value = None, key
1481 return self.append(key, value)
1483 def remove(self: AT, key: Union[Key, str]) -> AT:
1484 self._value.remove(key)
1486 if isinstance(key, Key):
1487 key = key.key
1489 if key is not None:
1490 dict.__delitem__(self, key)
1492 return self
1494 def setdefault(self, key: Union[Key, str], default: Any) -> Any:
1495 super().setdefault(key, default)
1496 return self[key]
1498 def __str__(self):
1499 return str(self.value)
1501 def copy(self: AT) -> AT:
1502 return copy.copy(self)
1504 def __repr__(self) -> str:
1505 return repr(self.value)
1507 def __iter__(self) -> Iterator[str]:
1508 return iter(self._value)
1510 def __len__(self) -> int:
1511 return len(self._value)
1513 def __delitem__(self, key: Union[Key, str]) -> None:
1514 self.remove(key)
1516 def __getitem__(self, key: Union[Key, str]) -> Item:
1517 return cast(Item, self._value[key])
1519 def __setitem__(self, key: Union[Key, str], value: Any) -> None:
1520 if not isinstance(value, Item):
1521 value = item(value, _parent=self)
1523 is_replace = key in self
1524 self._value[key] = value
1526 if key is not None:
1527 dict.__setitem__(self, key, value)
1529 if is_replace:
1530 return
1531 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1532 if not m:
1533 return
1535 indent = m.group(1)
1537 if not isinstance(value, Whitespace):
1538 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1539 if not m:
1540 value.trivia.indent = indent
1541 else:
1542 value.trivia.indent = m.group(1) + indent + m.group(2)
1545class Table(AbstractTable):
1546 """
1547 A table literal.
1548 """
1550 def __init__(
1551 self,
1552 value: "container.Container",
1553 trivia: Trivia,
1554 is_aot_element: bool,
1555 is_super_table: Optional[bool] = None,
1556 name: Optional[str] = None,
1557 display_name: Optional[str] = None,
1558 ) -> None:
1559 super().__init__(value, trivia)
1561 self.name = name
1562 self.display_name = display_name
1563 self._is_aot_element = is_aot_element
1564 self._is_super_table = is_super_table
1566 @property
1567 def discriminant(self) -> int:
1568 return 9
1570 def __copy__(self) -> "Table":
1571 return type(self)(
1572 self._value.copy(),
1573 self._trivia.copy(),
1574 self._is_aot_element,
1575 self._is_super_table,
1576 self.name,
1577 self.display_name,
1578 )
1580 def append(self, key, _item):
1581 """
1582 Appends a (key, item) to the table.
1583 """
1584 if not isinstance(_item, Item):
1585 _item = item(_item, _parent=self)
1587 self._value.append(key, _item)
1589 if isinstance(key, Key):
1590 key = next(iter(key)).key
1591 _item = self._value[key]
1593 if key is not None:
1594 dict.__setitem__(self, key, _item)
1596 m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1597 if not m:
1598 return self
1600 indent = m.group(1)
1602 if not isinstance(_item, Whitespace):
1603 m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
1604 if not m:
1605 _item.trivia.indent = indent
1606 else:
1607 _item.trivia.indent = m.group(1) + indent + m.group(2)
1609 return self
1611 def raw_append(self, key: Union[Key, str], _item: Any) -> "Table":
1612 """Similar to :meth:`append` but does not copy indentation."""
1613 if not isinstance(_item, Item):
1614 _item = item(_item)
1616 self._value.append(key, _item)
1618 if isinstance(key, Key):
1619 key = next(iter(key)).key
1620 _item = self._value[key]
1622 if key is not None:
1623 dict.__setitem__(self, key, _item)
1625 return self
1627 def is_aot_element(self) -> bool:
1628 """True if the table is the direct child of an AOT element."""
1629 return self._is_aot_element
1631 def is_super_table(self) -> bool:
1632 """A super table is the intermediate parent of a nested table as in [a.b.c].
1633 If true, it won't appear in the TOML representation."""
1634 if self._is_super_table is not None:
1635 return self._is_super_table
1636 # If the table has only one child and that child is a table, then it is a super table.
1637 if len(self) != 1:
1638 return False
1639 only_child = next(iter(self.values()))
1640 return isinstance(only_child, (Table, AoT))
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 self.display_name = None
1666 for child in self.values():
1667 if hasattr(child, "invalidate_display_name"):
1668 child.invalidate_display_name()
1670 def _getstate(self, protocol: int = 3) -> tuple:
1671 return (
1672 self._value,
1673 self._trivia,
1674 self._is_aot_element,
1675 self._is_super_table,
1676 self.name,
1677 self.display_name,
1678 )
1681class InlineTable(AbstractTable):
1682 """
1683 An inline table literal.
1684 """
1686 def __init__(
1687 self, value: "container.Container", trivia: Trivia, new: bool = False
1688 ) -> None:
1689 super().__init__(value, trivia)
1691 self._new = new
1693 @property
1694 def discriminant(self) -> int:
1695 return 10
1697 def append(self, key, _item):
1698 """
1699 Appends a (key, item) to the table.
1700 """
1701 if not isinstance(_item, Item):
1702 _item = item(_item, _parent=self)
1704 if not isinstance(_item, (Whitespace, Comment)):
1705 if not _item.trivia.indent and len(self._value) > 0 and not self._new:
1706 _item.trivia.indent = " "
1707 if _item.trivia.comment:
1708 _item.trivia.comment = ""
1710 self._value.append(key, _item)
1712 if isinstance(key, Key):
1713 key = key.key
1715 if key is not None:
1716 dict.__setitem__(self, key, _item)
1718 return self
1720 def as_string(self) -> str:
1721 buf = "{"
1722 last_item_idx = next(
1723 (
1724 i
1725 for i in range(len(self._value.body) - 1, -1, -1)
1726 if self._value.body[i][0] is not None
1727 ),
1728 None,
1729 )
1730 for i, (k, v) in enumerate(self._value.body):
1731 if k is None:
1732 if i == len(self._value.body) - 1:
1733 if self._new:
1734 buf = buf.rstrip(", ")
1735 else:
1736 buf = buf.rstrip(",")
1738 buf += v.as_string()
1740 continue
1742 v_trivia_trail = v.trivia.trail.replace("\n", "")
1743 buf += (
1744 f"{v.trivia.indent}"
1745 f'{k.as_string() + ("." if k.is_dotted() else "")}'
1746 f"{k.sep}"
1747 f"{v.as_string()}"
1748 f"{v.trivia.comment}"
1749 f"{v_trivia_trail}"
1750 )
1752 if last_item_idx is not None and i < last_item_idx:
1753 buf += ","
1754 if self._new:
1755 buf += " "
1757 buf += "}"
1759 return buf
1761 def __setitem__(self, key: Union[Key, str], value: Any) -> None:
1762 if hasattr(value, "trivia") and value.trivia.comment:
1763 value.trivia.comment = ""
1764 super().__setitem__(key, value)
1766 def __copy__(self) -> "InlineTable":
1767 return type(self)(self._value.copy(), self._trivia.copy(), self._new)
1769 def _getstate(self, protocol: int = 3) -> tuple:
1770 return (self._value, self._trivia)
1773class String(str, Item):
1774 """
1775 A string literal.
1776 """
1778 def __new__(cls, t, value, original, trivia):
1779 return super().__new__(cls, value)
1781 def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
1782 super().__init__(trivia)
1784 self._t = t
1785 self._original = original
1787 def unwrap(self) -> str:
1788 return str(self)
1790 @property
1791 def discriminant(self) -> int:
1792 return 11
1794 @property
1795 def value(self) -> str:
1796 return self
1798 def as_string(self) -> str:
1799 return f"{self._t.value}{decode(self._original)}{self._t.value}"
1801 def __add__(self: ItemT, other: str) -> ItemT:
1802 if not isinstance(other, str):
1803 return NotImplemented
1804 result = super().__add__(other)
1805 original = self._original + getattr(other, "_original", other)
1807 return self._new(result, original)
1809 def _new(self, result: str, original: str) -> "String":
1810 return String(self._t, result, original, self._trivia)
1812 def _getstate(self, protocol=3):
1813 return self._t, str(self), self._original, self._trivia
1815 @classmethod
1816 def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> "String":
1817 value = decode(value)
1819 invalid = type_.invalid_sequences
1820 if any(c in value for c in invalid):
1821 raise InvalidStringError(value, invalid, type_.value)
1823 escaped = type_.escaped_sequences
1824 string_value = escape_string(value, escaped) if escape and escaped else value
1826 return cls(type_, decode(value), string_value, Trivia())
1829class AoT(Item, _CustomList):
1830 """
1831 An array of table literal
1832 """
1834 def __init__(
1835 self, body: List[Table], name: Optional[str] = None, parsed: bool = False
1836 ) -> None:
1837 self.name = name
1838 self._body: List[Table] = []
1839 self._parsed = parsed
1841 super().__init__(Trivia(trail=""))
1843 for table in body:
1844 self.append(table)
1846 def unwrap(self) -> List[Dict[str, Any]]:
1847 unwrapped = []
1848 for t in self._body:
1849 if hasattr(t, "unwrap"):
1850 unwrapped.append(t.unwrap())
1851 else:
1852 unwrapped.append(t)
1853 return unwrapped
1855 @property
1856 def body(self) -> List[Table]:
1857 return self._body
1859 @property
1860 def discriminant(self) -> int:
1861 return 12
1863 @property
1864 def value(self) -> List[Dict[Any, Any]]:
1865 return [v.value for v in self._body]
1867 def __len__(self) -> int:
1868 return len(self._body)
1870 @overload
1871 def __getitem__(self, key: slice) -> List[Table]:
1872 ...
1874 @overload
1875 def __getitem__(self, key: int) -> Table:
1876 ...
1878 def __getitem__(self, key):
1879 return self._body[key]
1881 def __setitem__(self, key: Union[slice, int], value: Any) -> None:
1882 raise NotImplementedError
1884 def __delitem__(self, key: Union[slice, int]) -> None:
1885 del self._body[key]
1886 list.__delitem__(self, key)
1888 def insert(self, index: int, value: dict) -> None:
1889 value = item(value, _parent=self)
1890 if not isinstance(value, Table):
1891 raise ValueError(f"Unsupported insert value type: {type(value)}")
1892 length = len(self)
1893 if index < 0:
1894 index += length
1895 if index < 0:
1896 index = 0
1897 elif index >= length:
1898 index = length
1899 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1900 if m:
1901 indent = m.group(1)
1903 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1904 if not m:
1905 value.trivia.indent = indent
1906 else:
1907 value.trivia.indent = m.group(1) + indent + m.group(2)
1908 prev_table = self._body[index - 1] if 0 < index and length else None
1909 next_table = self._body[index + 1] if index < length - 1 else None
1910 if not self._parsed:
1911 if prev_table and "\n" not in value.trivia.indent:
1912 value.trivia.indent = "\n" + value.trivia.indent
1913 if next_table and "\n" not in next_table.trivia.indent:
1914 next_table.trivia.indent = "\n" + next_table.trivia.indent
1915 self._body.insert(index, value)
1916 list.insert(self, index, value)
1918 def invalidate_display_name(self):
1919 """Call ``invalidate_display_name`` on the contained tables"""
1920 for child in self:
1921 if hasattr(child, "invalidate_display_name"):
1922 child.invalidate_display_name()
1924 def as_string(self) -> str:
1925 b = ""
1926 for table in self._body:
1927 b += table.as_string()
1929 return b
1931 def __repr__(self) -> str:
1932 return f"<AoT {self.value}>"
1934 def _getstate(self, protocol=3):
1935 return self._body, self.name, self._parsed
1938class Null(Item):
1939 """
1940 A null item.
1941 """
1943 def __init__(self) -> None:
1944 pass
1946 def unwrap(self) -> None:
1947 return None
1949 @property
1950 def discriminant(self) -> int:
1951 return -1
1953 @property
1954 def value(self) -> None:
1955 return None
1957 def as_string(self) -> str:
1958 return ""
1960 def _getstate(self, protocol=3) -> tuple:
1961 return ()