Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/items.py: 69%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import abc
4import copy
5import dataclasses
6import inspect
7import math
8import re
9import string
10import sys
12from datetime import date
13from datetime import datetime
14from datetime import time
15from datetime import tzinfo
16from enum import Enum
17from typing import TYPE_CHECKING
18from typing import Any
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 typing import Protocol
43 from tomlkit import container
45 class Encoder(Protocol):
46 def __call__(
47 self, __value: Any, _parent: Item | None = None, _sort_keys: bool = False
48 ) -> Item: ...
51ItemT = TypeVar("ItemT", bound="Item")
52CUSTOM_ENCODERS: list[Encoder] = []
53AT = TypeVar("AT", bound="AbstractTable")
56@overload
57def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ...
60@overload
61def item(value: int, _parent: Item | None = ..., _sort_keys: bool = ...) -> Integer: ...
64@overload
65def item(value: float, _parent: Item | None = ..., _sort_keys: bool = ...) -> Float: ...
68@overload
69def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> String: ...
72@overload
73def item(
74 value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...
75) -> DateTime: ...
78@overload
79def item(value: date, _parent: Item | None = ..., _sort_keys: bool = ...) -> Date: ...
82@overload
83def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Time: ...
86@overload
87def item(
88 value: Sequence[dict], _parent: Item | None = ..., _sort_keys: bool = ...
89) -> AoT: ...
92@overload
93def item(
94 value: Sequence, _parent: Item | None = ..., _sort_keys: bool = ...
95) -> Array: ...
98@overload
99def item(value: dict, _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ...
102@overload
103def item(value: dict, _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ...
106@overload
107def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ...
110def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item:
111 """Create a TOML item from a Python object.
113 :Example:
115 >>> item(42)
116 42
117 >>> item([1, 2, 3])
118 [1, 2, 3]
119 >>> item({'a': 1, 'b': 2})
120 a = 1
121 b = 2
122 """
124 from tomlkit.container import Container
126 if isinstance(value, Item):
127 return value
129 if isinstance(value, bool):
130 return Bool(value, Trivia())
131 elif isinstance(value, int):
132 return Integer(value, Trivia(), str(value))
133 elif isinstance(value, float):
134 return Float(value, Trivia(), str(value))
135 elif isinstance(value, dict):
136 table_constructor = (
137 InlineTable if isinstance(_parent, (Array, InlineTable)) else Table
138 )
139 val = table_constructor(Container(), Trivia(), False)
140 for k, v in sorted(
141 value.items(),
142 key=lambda i: (isinstance(i[1], dict), i[0]) if _sort_keys else 1,
143 ):
144 val[k] = item(v, _parent=val, _sort_keys=_sort_keys)
146 return val
147 elif isinstance(value, (list, tuple)):
148 if (
149 value
150 and all(isinstance(v, dict) for v in value)
151 and (_parent is None or isinstance(_parent, Table))
152 ):
153 a = AoT([])
154 table_constructor = Table
155 else:
156 a = Array([], Trivia())
157 table_constructor = InlineTable
159 for v in value:
160 if isinstance(v, dict):
161 table = table_constructor(Container(), Trivia(), True)
163 for k, _v in sorted(
164 v.items(),
165 key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
166 ):
167 i = item(_v, _parent=table, _sort_keys=_sort_keys)
168 if isinstance(table, InlineTable):
169 i.trivia.trail = ""
171 table[k] = i
173 v = table
175 a.append(v)
177 return a
178 elif isinstance(value, str):
179 return String.from_raw(value)
180 elif isinstance(value, datetime):
181 return DateTime(
182 value.year,
183 value.month,
184 value.day,
185 value.hour,
186 value.minute,
187 value.second,
188 value.microsecond,
189 value.tzinfo,
190 Trivia(),
191 value.isoformat().replace("+00:00", "Z"),
192 )
193 elif isinstance(value, date):
194 return Date(value.year, value.month, value.day, Trivia(), value.isoformat())
195 elif isinstance(value, time):
196 return Time(
197 value.hour,
198 value.minute,
199 value.second,
200 value.microsecond,
201 value.tzinfo,
202 Trivia(),
203 value.isoformat(),
204 )
205 else:
206 for encoder in CUSTOM_ENCODERS:
207 try:
208 # Check if encoder accepts keyword arguments for backward compatibility
209 sig = inspect.signature(encoder)
210 if "_parent" in sig.parameters or any(
211 p.kind == p.VAR_KEYWORD for p in sig.parameters.values()
212 ):
213 # New style encoder that can accept additional parameters
214 rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys)
215 else:
216 # Old style encoder that only accepts value
217 rv = encoder(value)
218 except ConvertError:
219 pass
220 else:
221 if not isinstance(rv, Item):
222 raise ConvertError(
223 f"Custom encoder is expected to return an instance of Item, got {type(rv)}"
224 )
225 return rv
227 raise ConvertError(f"Unable to convert an object of {type(value)} to a TOML item")
230class StringType(Enum):
231 # Single Line Basic
232 SLB = '"'
233 # Multi Line Basic
234 MLB = '"""'
235 # Single Line Literal
236 SLL = "'"
237 # Multi Line Literal
238 MLL = "'''"
240 @classmethod
241 def select(cls, literal=False, multiline=False) -> StringType:
242 return {
243 (False, False): cls.SLB,
244 (False, True): cls.MLB,
245 (True, False): cls.SLL,
246 (True, True): cls.MLL,
247 }[(literal, multiline)]
249 @property
250 def escaped_sequences(self) -> Collection[str]:
251 # https://toml.io/en/v1.0.0#string
252 escaped_in_basic = CONTROL_CHARS | {"\\"}
253 allowed_in_multiline = {"\n", "\r"}
254 return {
255 StringType.SLB: escaped_in_basic | {'"'},
256 StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline,
257 StringType.SLL: (),
258 StringType.MLL: (),
259 }[self]
261 @property
262 def invalid_sequences(self) -> Collection[str]:
263 # https://toml.io/en/v1.0.0#string
264 forbidden_in_literal = CONTROL_CHARS - {"\t"}
265 allowed_in_multiline = {"\n", "\r"}
266 return {
267 StringType.SLB: (),
268 StringType.MLB: (),
269 StringType.SLL: forbidden_in_literal | {"'"},
270 StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline,
271 }[self]
273 @property
274 def unit(self) -> str:
275 return self.value[0]
277 def is_basic(self) -> bool:
278 return self in {StringType.SLB, StringType.MLB}
280 def is_literal(self) -> bool:
281 return self in {StringType.SLL, StringType.MLL}
283 def is_singleline(self) -> bool:
284 return self in {StringType.SLB, StringType.SLL}
286 def is_multiline(self) -> bool:
287 return self in {StringType.MLB, StringType.MLL}
289 def toggle(self) -> StringType:
290 return {
291 StringType.SLB: StringType.MLB,
292 StringType.MLB: StringType.SLB,
293 StringType.SLL: StringType.MLL,
294 StringType.MLL: StringType.SLL,
295 }[self]
298class BoolType(Enum):
299 TRUE = "true"
300 FALSE = "false"
302 def __bool__(self):
303 return {BoolType.TRUE: True, BoolType.FALSE: False}[self]
305 def __iter__(self):
306 return iter(self.value)
308 def __len__(self):
309 return len(self.value)
312@dataclasses.dataclass
313class Trivia:
314 """
315 Trivia information (aka metadata).
316 """
318 # Whitespace before a value.
319 indent: str = ""
320 # Whitespace after a value, but before a comment.
321 comment_ws: str = ""
322 # Comment, starting with # character, or empty string if no comment.
323 comment: str = ""
324 # Trailing newline.
325 trail: str = "\n"
327 def copy(self) -> Trivia:
328 return dataclasses.replace(self)
331class KeyType(Enum):
332 """
333 The type of a Key.
335 Keys can be bare (unquoted), or quoted using basic ("), or literal (')
336 quotes following the same escaping rules as single-line StringType.
337 """
339 Bare = ""
340 Basic = '"'
341 Literal = "'"
344class Key(abc.ABC):
345 """Base class for a key"""
347 sep: str
348 _original: str
349 _keys: list[SingleKey]
350 _dotted: bool
351 key: str
353 @abc.abstractmethod
354 def __hash__(self) -> int:
355 pass
357 @abc.abstractmethod
358 def __eq__(self, __o: object) -> bool:
359 pass
361 def is_dotted(self) -> bool:
362 """If the key is followed by other keys"""
363 return self._dotted
365 def __iter__(self) -> Iterator[SingleKey]:
366 return iter(self._keys)
368 def concat(self, other: Key) -> DottedKey:
369 """Concatenate keys into a dotted key"""
370 keys = self._keys + other._keys
371 return DottedKey(keys, sep=self.sep)
373 def is_multi(self) -> bool:
374 """Check if the key contains multiple keys"""
375 return len(self._keys) > 1
377 def as_string(self) -> str:
378 """The TOML representation"""
379 return self._original
381 def __str__(self) -> str:
382 return self.as_string()
384 def __repr__(self) -> str:
385 return f"<Key {self.as_string()}>"
388class SingleKey(Key):
389 """A single key"""
391 def __init__(
392 self,
393 k: str,
394 t: KeyType | None = None,
395 sep: str | None = None,
396 original: str | None = None,
397 ) -> None:
398 if not isinstance(k, str):
399 raise TypeError("Keys must be strings")
401 if t is None:
402 if not k or any(
403 c not in string.ascii_letters + string.digits + "-" + "_" for c in k
404 ):
405 t = KeyType.Basic
406 else:
407 t = KeyType.Bare
409 self.t = t
410 if sep is None:
411 sep = " = "
413 self.sep = sep
414 self.key = k
415 if original is None:
416 key_str = escape_string(k) if t == KeyType.Basic else k
417 original = f"{t.value}{key_str}{t.value}"
419 self._original = original
420 self._keys = [self]
421 self._dotted = False
423 @property
424 def delimiter(self) -> str:
425 """The delimiter: double quote/single quote/none"""
426 return self.t.value
428 def is_bare(self) -> bool:
429 """Check if the key is bare"""
430 return self.t == KeyType.Bare
432 def __hash__(self) -> int:
433 return hash(self.key)
435 def __eq__(self, other: Any) -> bool:
436 if isinstance(other, Key):
437 return isinstance(other, SingleKey) and self.key == other.key
439 return self.key == other
442class DottedKey(Key):
443 def __init__(
444 self,
445 keys: Iterable[SingleKey],
446 sep: str | None = None,
447 original: str | None = None,
448 ) -> None:
449 self._keys = list(keys)
450 if original is None:
451 original = ".".join(k.as_string() for k in self._keys)
453 self.sep = " = " if sep is None else sep
454 self._original = original
455 self._dotted = False
456 self.key = ".".join(k.key for k in self._keys)
458 def __hash__(self) -> int:
459 return hash(tuple(self._keys))
461 def __eq__(self, __o: object) -> bool:
462 return isinstance(__o, DottedKey) and self._keys == __o._keys
465class Item:
466 """
467 An item within a TOML document.
468 """
470 def __init__(self, trivia: Trivia) -> None:
471 self._trivia = trivia
473 @property
474 def trivia(self) -> Trivia:
475 """The trivia element associated with this item"""
476 return self._trivia
478 @property
479 def discriminant(self) -> int:
480 raise NotImplementedError()
482 def as_string(self) -> str:
483 """The TOML representation"""
484 raise NotImplementedError()
486 @property
487 def value(self) -> Any:
488 return self
490 def unwrap(self) -> Any:
491 """Returns as pure python object (ppo)"""
492 raise NotImplementedError()
494 # Helpers
496 def comment(self, comment: str) -> Item:
497 """Attach a comment to this item"""
498 if not comment.strip().startswith("#"):
499 comment = "# " + comment
501 self._trivia.comment_ws = " "
502 self._trivia.comment = comment
504 return self
506 def indent(self, indent: int) -> Item:
507 """Indent this item with given number of spaces"""
508 if self._trivia.indent.startswith("\n"):
509 self._trivia.indent = "\n" + " " * indent
510 else:
511 self._trivia.indent = " " * indent
513 return self
515 def is_boolean(self) -> bool:
516 return isinstance(self, Bool)
518 def is_table(self) -> bool:
519 return isinstance(self, Table)
521 def is_inline_table(self) -> bool:
522 return isinstance(self, InlineTable)
524 def is_aot(self) -> bool:
525 return isinstance(self, AoT)
527 def _getstate(self, protocol=3):
528 return (self._trivia,)
530 def __reduce__(self):
531 return self.__reduce_ex__(2)
533 def __reduce_ex__(self, protocol):
534 return self.__class__, self._getstate(protocol)
537class Whitespace(Item):
538 """
539 A whitespace literal.
540 """
542 def __init__(self, s: str, fixed: bool = False) -> None:
543 self._s = s
544 self._fixed = fixed
546 @property
547 def s(self) -> str:
548 return self._s
550 @property
551 def value(self) -> str:
552 """The wrapped string of the whitespace"""
553 return self._s
555 @property
556 def trivia(self) -> Trivia:
557 raise RuntimeError("Called trivia on a Whitespace variant.")
559 @property
560 def discriminant(self) -> int:
561 return 0
563 def is_fixed(self) -> bool:
564 """If the whitespace is fixed, it can't be merged or discarded from the output."""
565 return self._fixed
567 def as_string(self) -> str:
568 return self._s
570 def __repr__(self) -> str:
571 return f"<{self.__class__.__name__} {self._s!r}>"
573 def _getstate(self, protocol=3):
574 return self._s, self._fixed
577class Comment(Item):
578 """
579 A comment literal.
580 """
582 @property
583 def discriminant(self) -> int:
584 return 1
586 def as_string(self) -> str:
587 return (
588 f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
589 )
591 def __str__(self) -> str:
592 return f"{self._trivia.indent}{decode(self._trivia.comment)}"
595class Integer(Item, _CustomInt):
596 """
597 An integer literal.
598 """
600 def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
601 return int.__new__(cls, value)
603 def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
604 super().__init__(trivia)
605 self._original = value
606 self._raw = raw
607 self._sign = False
609 if re.match(r"^[+\-]\d+$", raw):
610 self._sign = True
612 def unwrap(self) -> int:
613 return self._original
615 __int__ = unwrap
617 def __hash__(self) -> int:
618 return hash(self.unwrap())
620 @property
621 def discriminant(self) -> int:
622 return 2
624 @property
625 def value(self) -> int:
626 """The wrapped integer value"""
627 return self
629 def as_string(self) -> str:
630 return self._raw
632 def _new(self, result):
633 raw = str(result)
634 if self._sign and result >= 0:
635 raw = f"+{raw}"
637 return Integer(result, self._trivia, raw)
639 def _getstate(self, protocol=3):
640 return int(self), self._trivia, self._raw
642 # int methods
643 __abs__ = wrap_method(int.__abs__)
644 __add__ = wrap_method(int.__add__)
645 __and__ = wrap_method(int.__and__)
646 __ceil__ = wrap_method(int.__ceil__)
647 __eq__ = int.__eq__
648 __floor__ = wrap_method(int.__floor__)
649 __floordiv__ = wrap_method(int.__floordiv__)
650 __invert__ = wrap_method(int.__invert__)
651 __le__ = int.__le__
652 __lshift__ = wrap_method(int.__lshift__)
653 __lt__ = int.__lt__
654 __mod__ = wrap_method(int.__mod__)
655 __mul__ = wrap_method(int.__mul__)
656 __neg__ = wrap_method(int.__neg__)
657 __or__ = wrap_method(int.__or__)
658 __pos__ = wrap_method(int.__pos__)
659 __pow__ = wrap_method(int.__pow__)
660 __radd__ = wrap_method(int.__radd__)
661 __rand__ = wrap_method(int.__rand__)
662 __rfloordiv__ = wrap_method(int.__rfloordiv__)
663 __rlshift__ = wrap_method(int.__rlshift__)
664 __rmod__ = wrap_method(int.__rmod__)
665 __rmul__ = wrap_method(int.__rmul__)
666 __ror__ = wrap_method(int.__ror__)
667 __round__ = wrap_method(int.__round__)
668 __rpow__ = wrap_method(int.__rpow__)
669 __rrshift__ = wrap_method(int.__rrshift__)
670 __rshift__ = wrap_method(int.__rshift__)
671 __rxor__ = wrap_method(int.__rxor__)
672 __trunc__ = wrap_method(int.__trunc__)
673 __xor__ = wrap_method(int.__xor__)
675 def __rtruediv__(self, other):
676 result = int.__rtruediv__(self, other)
677 if result is NotImplemented:
678 return result
679 return Float._new(self, result)
681 def __truediv__(self, other):
682 result = int.__truediv__(self, other)
683 if result is NotImplemented:
684 return result
685 return Float._new(self, result)
688class Float(Item, _CustomFloat):
689 """
690 A float literal.
691 """
693 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
694 return float.__new__(cls, value)
696 def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
697 super().__init__(trivia)
698 self._original = value
699 self._raw = raw
700 self._sign = False
702 if re.match(r"^[+\-].+$", raw):
703 self._sign = True
705 def unwrap(self) -> float:
706 return self._original
708 __float__ = unwrap
710 def __hash__(self) -> int:
711 return hash(self.unwrap())
713 @property
714 def discriminant(self) -> int:
715 return 3
717 @property
718 def value(self) -> float:
719 """The wrapped float value"""
720 return self
722 def as_string(self) -> str:
723 return self._raw
725 def _new(self, result):
726 raw = str(result)
728 if self._sign and result >= 0:
729 raw = f"+{raw}"
731 return Float(result, self._trivia, raw)
733 def _getstate(self, protocol=3):
734 return float(self), self._trivia, self._raw
736 # float methods
737 __abs__ = wrap_method(float.__abs__)
738 __add__ = wrap_method(float.__add__)
739 __eq__ = float.__eq__
740 __floordiv__ = wrap_method(float.__floordiv__)
741 __le__ = float.__le__
742 __lt__ = float.__lt__
743 __mod__ = wrap_method(float.__mod__)
744 __mul__ = wrap_method(float.__mul__)
745 __neg__ = wrap_method(float.__neg__)
746 __pos__ = wrap_method(float.__pos__)
747 __pow__ = wrap_method(float.__pow__)
748 __radd__ = wrap_method(float.__radd__)
749 __rfloordiv__ = wrap_method(float.__rfloordiv__)
750 __rmod__ = wrap_method(float.__rmod__)
751 __rmul__ = wrap_method(float.__rmul__)
752 __round__ = wrap_method(float.__round__)
753 __rpow__ = wrap_method(float.__rpow__)
754 __rtruediv__ = wrap_method(float.__rtruediv__)
755 __truediv__ = wrap_method(float.__truediv__)
756 __trunc__ = float.__trunc__
758 if sys.version_info >= (3, 9):
759 __ceil__ = float.__ceil__
760 __floor__ = float.__floor__
761 else:
762 __ceil__ = math.ceil
763 __floor__ = math.floor
766class Bool(Item):
767 """
768 A boolean literal.
769 """
771 def __init__(self, t: int, trivia: Trivia) -> None:
772 super().__init__(trivia)
774 self._value = bool(t)
776 def unwrap(self) -> bool:
777 return bool(self)
779 @property
780 def discriminant(self) -> int:
781 return 4
783 @property
784 def value(self) -> bool:
785 """The wrapped boolean value"""
786 return self._value
788 def as_string(self) -> str:
789 return str(self._value).lower()
791 def _getstate(self, protocol=3):
792 return self._value, self._trivia
794 def __bool__(self):
795 return self._value
797 __nonzero__ = __bool__
799 def __eq__(self, other):
800 if not isinstance(other, bool):
801 return NotImplemented
803 return other == self._value
805 def __hash__(self):
806 return hash(self._value)
808 def __repr__(self):
809 return repr(self._value)
812class DateTime(Item, datetime):
813 """
814 A datetime literal.
815 """
817 def __new__(
818 cls,
819 year: int,
820 month: int,
821 day: int,
822 hour: int,
823 minute: int,
824 second: int,
825 microsecond: int,
826 tzinfo: tzinfo | None,
827 *_: Any,
828 **kwargs: Any,
829 ) -> datetime:
830 return datetime.__new__(
831 cls,
832 year,
833 month,
834 day,
835 hour,
836 minute,
837 second,
838 microsecond,
839 tzinfo=tzinfo,
840 **kwargs,
841 )
843 def __init__(
844 self,
845 year: int,
846 month: int,
847 day: int,
848 hour: int,
849 minute: int,
850 second: int,
851 microsecond: int,
852 tzinfo: tzinfo | None,
853 trivia: Trivia | None = None,
854 raw: str | None = None,
855 **kwargs: Any,
856 ) -> None:
857 super().__init__(trivia or Trivia())
859 self._raw = raw or self.isoformat()
861 def unwrap(self) -> datetime:
862 (
863 year,
864 month,
865 day,
866 hour,
867 minute,
868 second,
869 microsecond,
870 tzinfo,
871 _,
872 _,
873 ) = self._getstate()
874 return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
876 @property
877 def discriminant(self) -> int:
878 return 5
880 @property
881 def value(self) -> datetime:
882 return self
884 def as_string(self) -> str:
885 return self._raw
887 def __add__(self, other):
888 if PY38:
889 result = datetime(
890 self.year,
891 self.month,
892 self.day,
893 self.hour,
894 self.minute,
895 self.second,
896 self.microsecond,
897 self.tzinfo,
898 ).__add__(other)
899 else:
900 result = super().__add__(other)
902 return self._new(result)
904 def __sub__(self, other):
905 if PY38:
906 result = datetime(
907 self.year,
908 self.month,
909 self.day,
910 self.hour,
911 self.minute,
912 self.second,
913 self.microsecond,
914 self.tzinfo,
915 ).__sub__(other)
916 else:
917 result = super().__sub__(other)
919 if isinstance(result, datetime):
920 result = self._new(result)
922 return result
924 def replace(self, *args: Any, **kwargs: Any) -> datetime:
925 return self._new(super().replace(*args, **kwargs))
927 def astimezone(self, tz: tzinfo) -> datetime:
928 result = super().astimezone(tz)
929 if PY38:
930 return result
931 return self._new(result)
933 def _new(self, result) -> DateTime:
934 raw = result.isoformat()
936 return DateTime(
937 result.year,
938 result.month,
939 result.day,
940 result.hour,
941 result.minute,
942 result.second,
943 result.microsecond,
944 result.tzinfo,
945 self._trivia,
946 raw,
947 )
949 def _getstate(self, protocol=3):
950 return (
951 self.year,
952 self.month,
953 self.day,
954 self.hour,
955 self.minute,
956 self.second,
957 self.microsecond,
958 self.tzinfo,
959 self._trivia,
960 self._raw,
961 )
964class Date(Item, date):
965 """
966 A date literal.
967 """
969 def __new__(cls, year: int, month: int, day: int, *_: Any) -> date:
970 return date.__new__(cls, year, month, day)
972 def __init__(
973 self,
974 year: int,
975 month: int,
976 day: int,
977 trivia: Trivia | None = None,
978 raw: str = "",
979 ) -> None:
980 super().__init__(trivia or Trivia())
982 self._raw = raw
984 def unwrap(self) -> date:
985 (year, month, day, _, _) = self._getstate()
986 return date(year, month, day)
988 @property
989 def discriminant(self) -> int:
990 return 6
992 @property
993 def value(self) -> date:
994 return self
996 def as_string(self) -> str:
997 return self._raw
999 def __add__(self, other):
1000 if PY38:
1001 result = date(self.year, self.month, self.day).__add__(other)
1002 else:
1003 result = super().__add__(other)
1005 return self._new(result)
1007 def __sub__(self, other):
1008 if PY38:
1009 result = date(self.year, self.month, self.day).__sub__(other)
1010 else:
1011 result = super().__sub__(other)
1013 if isinstance(result, date):
1014 result = self._new(result)
1016 return result
1018 def replace(self, *args: Any, **kwargs: Any) -> date:
1019 return self._new(super().replace(*args, **kwargs))
1021 def _new(self, result):
1022 raw = result.isoformat()
1024 return Date(result.year, result.month, result.day, self._trivia, raw)
1026 def _getstate(self, protocol=3):
1027 return (self.year, self.month, self.day, self._trivia, self._raw)
1030class Time(Item, time):
1031 """
1032 A time literal.
1033 """
1035 def __new__(
1036 cls,
1037 hour: int,
1038 minute: int,
1039 second: int,
1040 microsecond: int,
1041 tzinfo: tzinfo | None,
1042 *_: Any,
1043 ) -> time:
1044 return time.__new__(cls, hour, minute, second, microsecond, tzinfo)
1046 def __init__(
1047 self,
1048 hour: int,
1049 minute: int,
1050 second: int,
1051 microsecond: int,
1052 tzinfo: tzinfo | None,
1053 trivia: Trivia | None = None,
1054 raw: str = "",
1055 ) -> None:
1056 super().__init__(trivia or Trivia())
1058 self._raw = raw
1060 def unwrap(self) -> time:
1061 (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
1062 return time(hour, minute, second, microsecond, tzinfo)
1064 @property
1065 def discriminant(self) -> int:
1066 return 7
1068 @property
1069 def value(self) -> time:
1070 return self
1072 def as_string(self) -> str:
1073 return self._raw
1075 def replace(self, *args: Any, **kwargs: Any) -> time:
1076 return self._new(super().replace(*args, **kwargs))
1078 def _new(self, result):
1079 raw = result.isoformat()
1081 return Time(
1082 result.hour,
1083 result.minute,
1084 result.second,
1085 result.microsecond,
1086 result.tzinfo,
1087 self._trivia,
1088 raw,
1089 )
1091 def _getstate(self, protocol: int = 3) -> tuple:
1092 return (
1093 self.hour,
1094 self.minute,
1095 self.second,
1096 self.microsecond,
1097 self.tzinfo,
1098 self._trivia,
1099 self._raw,
1100 )
1103class _ArrayItemGroup:
1104 __slots__ = ("comma", "comment", "indent", "value")
1106 def __init__(
1107 self,
1108 value: Item | None = None,
1109 indent: Whitespace | None = None,
1110 comma: Whitespace | None = None,
1111 comment: Comment | None = None,
1112 ) -> None:
1113 self.value = value
1114 self.indent = indent
1115 self.comma = comma
1116 self.comment = comment
1118 def __iter__(self) -> Iterator[Item]:
1119 return filter(
1120 lambda x: x is not None, (self.indent, self.value, self.comma, self.comment)
1121 )
1123 def __repr__(self) -> str:
1124 return repr(tuple(self))
1126 def is_whitespace(self) -> bool:
1127 return self.value is None and self.comment is None
1129 def __bool__(self) -> bool:
1130 try:
1131 next(iter(self))
1132 except StopIteration:
1133 return False
1134 return True
1137class Array(Item, _CustomList):
1138 """
1139 An array literal
1140 """
1142 def __init__(
1143 self, value: list[Item], trivia: Trivia, multiline: bool = False
1144 ) -> None:
1145 super().__init__(trivia)
1146 list.__init__(
1147 self,
1148 [v for v in value if not isinstance(v, (Whitespace, Comment, Null))],
1149 )
1150 self._index_map: dict[int, int] = {}
1151 self._value = self._group_values(value)
1152 self._multiline = multiline
1153 self._reindex()
1155 def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
1156 """Group the values into (indent, value, comma, comment) tuples"""
1157 groups = []
1158 this_group = _ArrayItemGroup()
1159 start_new_group = False
1160 for item in value:
1161 if isinstance(item, Whitespace):
1162 if "," not in item.s or start_new_group:
1163 groups.append(this_group)
1164 this_group = _ArrayItemGroup(indent=item)
1165 start_new_group = False
1166 else:
1167 if this_group.value is None:
1168 # when comma is met and no value is provided, add a dummy Null
1169 this_group.value = Null()
1170 this_group.comma = item
1171 elif isinstance(item, Comment):
1172 if this_group.value is None:
1173 this_group.value = Null()
1174 this_group.comment = item
1175 # Comments are the last item in a group.
1176 start_new_group = True
1177 elif this_group.value is None:
1178 this_group.value = item
1179 else:
1180 groups.append(this_group)
1181 this_group = _ArrayItemGroup(value=item)
1182 groups.append(this_group)
1183 return [group for group in groups if group]
1185 def unwrap(self) -> list[Any]:
1186 unwrapped = []
1187 for v in self:
1188 if hasattr(v, "unwrap"):
1189 unwrapped.append(v.unwrap())
1190 else:
1191 unwrapped.append(v)
1192 return unwrapped
1194 @property
1195 def discriminant(self) -> int:
1196 return 8
1198 @property
1199 def value(self) -> list:
1200 return self
1202 def _iter_items(self) -> Iterator[Item]:
1203 for v in self._value:
1204 yield from v
1206 def multiline(self, multiline: bool) -> Array:
1207 """Change the array to display in multiline or not.
1209 :Example:
1211 >>> a = item([1, 2, 3])
1212 >>> print(a.as_string())
1213 [1, 2, 3]
1214 >>> print(a.multiline(True).as_string())
1215 [
1216 1,
1217 2,
1218 3,
1219 ]
1220 """
1221 self._multiline = multiline
1223 return self
1225 def as_string(self) -> str:
1226 if not self._multiline or not self._value:
1227 return f"[{''.join(v.as_string() for v in self._iter_items())}]"
1229 s = "[\n"
1230 s += "".join(
1231 self.trivia.indent
1232 + " " * 4
1233 + v.value.as_string()
1234 + ("," if not isinstance(v.value, Null) else "")
1235 + (v.comment.as_string() if v.comment is not None else "")
1236 + "\n"
1237 for v in self._value
1238 if v.value is not None
1239 )
1240 s += self.trivia.indent + "]"
1242 return s
1244 def _reindex(self) -> None:
1245 self._index_map.clear()
1246 index = 0
1247 for i, v in enumerate(self._value):
1248 if v.value is None or isinstance(v.value, Null):
1249 continue
1250 self._index_map[index] = i
1251 index += 1
1253 def add_line(
1254 self,
1255 *items: Any,
1256 indent: str = " ",
1257 comment: str | None = None,
1258 add_comma: bool = True,
1259 newline: bool = True,
1260 ) -> None:
1261 """Add multiple items in a line to control the format precisely.
1262 When add_comma is True, only accept actual values and
1263 ", " will be added between values automatically.
1265 :Example:
1267 >>> a = array()
1268 >>> a.add_line(1, 2, 3)
1269 >>> a.add_line(4, 5, 6)
1270 >>> a.add_line(indent="")
1271 >>> print(a.as_string())
1272 [
1273 1, 2, 3,
1274 4, 5, 6,
1275 ]
1276 """
1277 new_values: list[Item] = []
1278 first_indent = f"\n{indent}" if newline else indent
1279 if first_indent:
1280 new_values.append(Whitespace(first_indent))
1281 whitespace = ""
1282 data_values = []
1283 for i, el in enumerate(items):
1284 it = item(el, _parent=self)
1285 if isinstance(it, Comment) or (add_comma and isinstance(el, Whitespace)):
1286 raise ValueError(f"item type {type(it)} is not allowed in add_line")
1287 if not isinstance(it, Whitespace):
1288 if whitespace:
1289 new_values.append(Whitespace(whitespace))
1290 whitespace = ""
1291 new_values.append(it)
1292 data_values.append(it.value)
1293 if add_comma:
1294 new_values.append(Whitespace(","))
1295 if i != len(items) - 1:
1296 new_values.append(Whitespace(" "))
1297 elif "," not in it.s:
1298 whitespace += it.s
1299 else:
1300 new_values.append(it)
1301 if whitespace:
1302 new_values.append(Whitespace(whitespace))
1303 if comment:
1304 indent = " " if items else ""
1305 new_values.append(
1306 Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
1307 )
1308 list.extend(self, data_values)
1309 if len(self._value) > 0:
1310 last_item = self._value[-1]
1311 last_value_item = next(
1312 (
1313 v
1314 for v in self._value[::-1]
1315 if v.value is not None and not isinstance(v.value, Null)
1316 ),
1317 None,
1318 )
1319 if last_value_item is not None:
1320 last_value_item.comma = Whitespace(",")
1321 if last_item.is_whitespace():
1322 self._value[-1:-1] = self._group_values(new_values)
1323 else:
1324 self._value.extend(self._group_values(new_values))
1325 else:
1326 self._value.extend(self._group_values(new_values))
1327 self._reindex()
1329 def clear(self) -> None:
1330 """Clear the array."""
1331 list.clear(self)
1332 self._index_map.clear()
1333 self._value.clear()
1335 def __len__(self) -> int:
1336 return list.__len__(self)
1338 def item(self, index: int) -> Item:
1339 rv = list.__getitem__(self, index)
1340 return cast(Item, rv)
1342 def __getitem__(self, key: int | slice) -> Any:
1343 rv = list.__getitem__(self, key)
1344 if isinstance(rv, Bool):
1345 return rv.value
1346 return rv
1348 def __setitem__(self, key: int | slice, value: Any) -> Any:
1349 it = item(value, _parent=self)
1350 list.__setitem__(self, key, it)
1351 if isinstance(key, slice):
1352 raise ValueError("slice assignment is not supported")
1353 if key < 0:
1354 key += len(self)
1355 self._value[self._index_map[key]].value = it
1357 def insert(self, pos: int, value: Any) -> None:
1358 it = item(value, _parent=self)
1359 length = len(self)
1360 if not isinstance(it, (Comment, Whitespace)):
1361 list.insert(self, pos, it)
1362 if pos < 0:
1363 pos += length
1364 if pos < 0:
1365 pos = 0
1367 idx = 0 # insert position of the self._value list
1368 default_indent = " "
1369 if pos < length:
1370 try:
1371 idx = self._index_map[pos]
1372 except KeyError as e:
1373 raise IndexError("list index out of range") from e
1374 else:
1375 idx = len(self._value)
1376 if idx >= 1 and self._value[idx - 1].is_whitespace():
1377 # The last item is a pure whitespace(\n ), insert before it
1378 idx -= 1
1379 if (
1380 self._value[idx].indent is not None
1381 and "\n" in self._value[idx].indent.s
1382 ):
1383 default_indent = "\n "
1384 indent: Item | None = None
1385 comma: Item | None = Whitespace(",") if pos < length else None
1386 if idx < len(self._value) and not self._value[idx].is_whitespace():
1387 # Prefer to copy the indentation from the item after
1388 indent = self._value[idx].indent
1389 if idx > 0:
1390 last_item = self._value[idx - 1]
1391 if indent is None:
1392 indent = last_item.indent
1393 if not isinstance(last_item.value, Null) and "\n" in default_indent:
1394 # Copy the comma from the last item if 1) it contains a value and
1395 # 2) the array is multiline
1396 comma = last_item.comma
1397 if last_item.comma is None and not isinstance(last_item.value, Null):
1398 # Add comma to the last item to separate it from the following items.
1399 last_item.comma = Whitespace(",")
1400 if indent is None and (idx > 0 or "\n" in default_indent):
1401 # apply default indent if it isn't the first item or the array is multiline.
1402 indent = Whitespace(default_indent)
1403 new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
1404 self._value.insert(idx, new_item)
1405 self._reindex()
1407 def __delitem__(self, key: int | slice):
1408 length = len(self)
1409 list.__delitem__(self, key)
1411 if isinstance(key, slice):
1412 indices_to_remove = list(
1413 range(key.start or 0, key.stop or length, key.step or 1)
1414 )
1415 else:
1416 indices_to_remove = [length + key if key < 0 else key]
1417 for i in sorted(indices_to_remove, reverse=True):
1418 try:
1419 idx = self._index_map[i]
1420 except KeyError as e:
1421 if not isinstance(key, slice):
1422 raise IndexError("list index out of range") from e
1423 else:
1424 group_rm = self._value[idx]
1425 del self._value[idx]
1426 if (
1427 idx == 0
1428 and len(self._value) > 0
1429 and self._value[idx].indent
1430 and "\n" not in self._value[idx].indent.s
1431 ):
1432 # Remove the indentation of the first item if not newline
1433 self._value[idx].indent = None
1434 comma_in_indent = (
1435 group_rm.indent is not None and "," in group_rm.indent.s
1436 )
1437 comma_in_comma = group_rm.comma is not None and "," in group_rm.comma.s
1438 if comma_in_indent and comma_in_comma:
1439 # Removed group had both commas. Add one to the next group.
1440 group = self._value[idx] if len(self._value) > idx else None
1441 if group is not None:
1442 if group.indent is None:
1443 group.indent = Whitespace(",")
1444 elif "," not in group.indent.s:
1445 # Insert the comma after the newline
1446 try:
1447 newline_index = group.indent.s.index("\n")
1448 group.indent._s = (
1449 group.indent.s[: newline_index + 1]
1450 + ","
1451 + group.indent.s[newline_index + 1 :]
1452 )
1453 except ValueError:
1454 group.indent._s = "," + group.indent.s
1455 elif not comma_in_indent and not comma_in_comma:
1456 # Removed group had no commas. Remove the next comma found.
1457 for j in range(idx, len(self._value)):
1458 group = self._value[j]
1459 if group.indent is not None and "," in group.indent.s:
1460 group.indent._s = group.indent.s.replace(",", "", 1)
1461 break
1462 if group_rm.indent is not None and "\n" in group_rm.indent.s:
1463 # Restore the removed group's newline onto the next group
1464 # if the next group does not have a newline.
1465 # i.e. the two were on the same line
1466 group = self._value[idx] if len(self._value) > idx else None
1467 if group is not None and (
1468 group.indent is None or "\n" not in group.indent.s
1469 ):
1470 group.indent = group_rm.indent
1472 if len(self._value) > 0:
1473 v = self._value[-1]
1474 if not v.is_whitespace():
1475 # remove the comma of the last item
1476 v.comma = None
1478 self._reindex()
1480 def _getstate(self, protocol=3):
1481 return list(self._iter_items()), self._trivia, self._multiline
1484class AbstractTable(Item, _CustomDict):
1485 """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
1487 def __init__(self, value: container.Container, trivia: Trivia):
1488 Item.__init__(self, trivia)
1490 self._value = value
1492 for k, v in self._value.body:
1493 if k is not None:
1494 dict.__setitem__(self, k.key, v)
1496 def unwrap(self) -> dict[str, Any]:
1497 unwrapped = {}
1498 for k, v in self.items():
1499 if isinstance(k, Key):
1500 k = k.key
1501 if hasattr(v, "unwrap"):
1502 v = v.unwrap()
1503 unwrapped[k] = v
1505 return unwrapped
1507 @property
1508 def value(self) -> container.Container:
1509 return self._value
1511 @overload
1512 def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ...
1514 @overload
1515 def append(self: AT, key: Key | str, value: Any) -> AT: ...
1517 def append(self, key, value):
1518 raise NotImplementedError
1520 @overload
1521 def add(self: AT, key: Comment | Whitespace) -> AT: ...
1523 @overload
1524 def add(self: AT, key: Key | str, value: Any = ...) -> AT: ...
1526 def add(self, key, value=None):
1527 if value is None:
1528 if not isinstance(key, (Comment, Whitespace)):
1529 msg = "Non comment/whitespace items must have an associated key"
1530 raise ValueError(msg)
1532 key, value = None, key
1534 return self.append(key, value)
1536 def remove(self: AT, key: Key | str) -> AT:
1537 self._value.remove(key)
1539 if isinstance(key, Key):
1540 key = key.key
1542 if key is not None:
1543 dict.__delitem__(self, key)
1545 return self
1547 def item(self, key: Key | str) -> Item:
1548 return self._value.item(key)
1550 def setdefault(self, key: Key | str, default: Any) -> Any:
1551 super().setdefault(key, default)
1552 return self[key]
1554 def __str__(self):
1555 return str(self.value)
1557 def copy(self: AT) -> AT:
1558 return copy.copy(self)
1560 def __repr__(self) -> str:
1561 return repr(self.value)
1563 def __iter__(self) -> Iterator[str]:
1564 return iter(self._value)
1566 def __len__(self) -> int:
1567 return len(self._value)
1569 def __delitem__(self, key: Key | str) -> None:
1570 self.remove(key)
1572 def __getitem__(self, key: Key | str) -> Item:
1573 return cast(Item, self._value[key])
1575 def __setitem__(self, key: Key | str, value: Any) -> None:
1576 if not isinstance(value, Item):
1577 value = item(value, _parent=self)
1579 is_replace = key in self
1580 self._value[key] = value
1582 if key is not None:
1583 dict.__setitem__(self, key, value)
1585 if is_replace:
1586 return
1587 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1588 if not m:
1589 return
1591 indent = m.group(1)
1593 if not isinstance(value, Whitespace):
1594 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1595 if not m:
1596 value.trivia.indent = indent
1597 else:
1598 value.trivia.indent = m.group(1) + indent + m.group(2)
1601class Table(AbstractTable):
1602 """
1603 A table literal.
1604 """
1606 def __init__(
1607 self,
1608 value: container.Container,
1609 trivia: Trivia,
1610 is_aot_element: bool,
1611 is_super_table: bool | None = None,
1612 name: str | None = None,
1613 display_name: str | None = None,
1614 ) -> None:
1615 super().__init__(value, trivia)
1617 self.name = name
1618 self.display_name = display_name
1619 self._is_aot_element = is_aot_element
1620 self._is_super_table = is_super_table
1622 @property
1623 def discriminant(self) -> int:
1624 return 9
1626 def __copy__(self) -> Table:
1627 return type(self)(
1628 self._value.copy(),
1629 self._trivia.copy(),
1630 self._is_aot_element,
1631 self._is_super_table,
1632 self.name,
1633 self.display_name,
1634 )
1636 def append(self, key: Key | str | None, _item: Any) -> Table:
1637 """
1638 Appends a (key, item) to the table.
1639 """
1640 if not isinstance(_item, Item):
1641 _item = item(_item, _parent=self)
1643 self._value.append(key, _item)
1645 if isinstance(key, Key):
1646 key = next(iter(key)).key
1647 _item = self._value[key]
1649 if key is not None:
1650 dict.__setitem__(self, key, _item)
1652 m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1653 if not m:
1654 return self
1656 indent = m.group(1)
1658 if not isinstance(_item, Whitespace):
1659 m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
1660 if not m:
1661 _item.trivia.indent = indent
1662 else:
1663 _item.trivia.indent = m.group(1) + indent + m.group(2)
1665 return self
1667 def raw_append(self, key: Key | str | None, _item: Any) -> Table:
1668 """Similar to :meth:`append` but does not copy indentation."""
1669 if not isinstance(_item, Item):
1670 _item = item(_item)
1672 self._value.append(key, _item, validate=False)
1674 if isinstance(key, Key):
1675 key = next(iter(key)).key
1676 _item = self._value[key]
1678 if key is not None:
1679 dict.__setitem__(self, key, _item)
1681 return self
1683 def is_aot_element(self) -> bool:
1684 """True if the table is the direct child of an AOT element."""
1685 return self._is_aot_element
1687 def is_super_table(self) -> bool:
1688 """A super table is the intermediate parent of a nested table as in [a.b.c].
1689 If true, it won't appear in the TOML representation."""
1690 if self._is_super_table is not None:
1691 return self._is_super_table
1692 if not self:
1693 return False
1694 # If the table has children and all children are tables, then it is a super table.
1695 for k, child in self.items():
1696 if not isinstance(k, Key):
1697 k = SingleKey(k)
1698 index = self.value._map[k]
1699 if isinstance(index, tuple):
1700 return False
1701 real_key = self.value.body[index][0]
1702 if (
1703 not isinstance(child, (Table, AoT))
1704 or real_key is None
1705 or real_key.is_dotted()
1706 ):
1707 return False
1708 return True
1710 def as_string(self) -> str:
1711 return self._value.as_string()
1713 # Helpers
1715 def indent(self, indent: int) -> Table:
1716 """Indent the table with given number of spaces."""
1717 super().indent(indent)
1719 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1720 if not m:
1721 indent_str = ""
1722 else:
1723 indent_str = m.group(1)
1725 for _, item in self._value.body:
1726 if not isinstance(item, Whitespace):
1727 item.trivia.indent = indent_str + item.trivia.indent
1729 return self
1731 def invalidate_display_name(self):
1732 """Call ``invalidate_display_name`` on the contained tables"""
1733 self.display_name = None
1735 for child in self.values():
1736 if hasattr(child, "invalidate_display_name"):
1737 child.invalidate_display_name()
1739 def _getstate(self, protocol: int = 3) -> tuple:
1740 return (
1741 self._value,
1742 self._trivia,
1743 self._is_aot_element,
1744 self._is_super_table,
1745 self.name,
1746 self.display_name,
1747 )
1750class InlineTable(AbstractTable):
1751 """
1752 An inline table literal.
1753 """
1755 def __init__(
1756 self, value: container.Container, trivia: Trivia, new: bool = False
1757 ) -> None:
1758 super().__init__(value, trivia)
1760 self._new = new
1762 @property
1763 def discriminant(self) -> int:
1764 return 10
1766 def append(self, key: Key | str | None, _item: Any) -> InlineTable:
1767 """
1768 Appends a (key, item) to the table.
1769 """
1770 if not isinstance(_item, Item):
1771 _item = item(_item, _parent=self)
1773 if not isinstance(_item, (Whitespace, Comment)):
1774 if not _item.trivia.indent and len(self._value) > 0 and not self._new:
1775 _item.trivia.indent = " "
1776 if _item.trivia.comment:
1777 _item.trivia.comment = ""
1779 self._value.append(key, _item)
1781 if isinstance(key, Key):
1782 key = key.key
1784 if key is not None:
1785 dict.__setitem__(self, key, _item)
1787 return self
1789 def as_string(self) -> str:
1790 buf = "{"
1791 last_item_idx = next(
1792 (
1793 i
1794 for i in range(len(self._value.body) - 1, -1, -1)
1795 if self._value.body[i][0] is not None
1796 ),
1797 None,
1798 )
1799 for i, (k, v) in enumerate(self._value.body):
1800 if k is None:
1801 if i == len(self._value.body) - 1:
1802 if self._new:
1803 buf = buf.rstrip(", ")
1804 else:
1805 buf = buf.rstrip(",")
1807 buf += v.as_string()
1809 continue
1811 v_trivia_trail = v.trivia.trail.replace("\n", "")
1812 buf += (
1813 f"{v.trivia.indent}"
1814 f"{k.as_string() + ('.' if k.is_dotted() else '')}"
1815 f"{k.sep}"
1816 f"{v.as_string()}"
1817 f"{v.trivia.comment}"
1818 f"{v_trivia_trail}"
1819 )
1821 if last_item_idx is not None and i < last_item_idx:
1822 buf += ","
1823 if self._new:
1824 buf += " "
1826 buf += "}"
1828 return buf
1830 def __setitem__(self, key: Key | str, value: Any) -> None:
1831 if hasattr(value, "trivia") and value.trivia.comment:
1832 value.trivia.comment = ""
1833 super().__setitem__(key, value)
1835 def __copy__(self) -> InlineTable:
1836 return type(self)(self._value.copy(), self._trivia.copy(), self._new)
1838 def _getstate(self, protocol: int = 3) -> tuple:
1839 return (self._value, self._trivia)
1842class String(str, Item):
1843 """
1844 A string literal.
1845 """
1847 def __new__(cls, t, value, original, trivia):
1848 return super().__new__(cls, value)
1850 def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
1851 super().__init__(trivia)
1853 self._t = t
1854 self._original = original
1856 def unwrap(self) -> str:
1857 return str(self)
1859 @property
1860 def discriminant(self) -> int:
1861 return 11
1863 @property
1864 def value(self) -> str:
1865 return self
1867 def as_string(self) -> str:
1868 return f"{self._t.value}{decode(self._original)}{self._t.value}"
1870 def __add__(self: ItemT, other: str) -> ItemT:
1871 if not isinstance(other, str):
1872 return NotImplemented
1873 result = super().__add__(other)
1874 original = self._original + getattr(other, "_original", other)
1876 return self._new(result, original)
1878 def _new(self, result: str, original: str) -> String:
1879 return String(self._t, result, original, self._trivia)
1881 def _getstate(self, protocol=3):
1882 return self._t, str(self), self._original, self._trivia
1884 @classmethod
1885 def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String:
1886 value = decode(value)
1888 invalid = type_.invalid_sequences
1889 if any(c in value for c in invalid):
1890 raise InvalidStringError(value, invalid, type_.value)
1892 escaped = type_.escaped_sequences
1893 string_value = escape_string(value, escaped) if escape and escaped else value
1895 return cls(type_, decode(value), string_value, Trivia())
1898class AoT(Item, _CustomList):
1899 """
1900 An array of table literal
1901 """
1903 def __init__(
1904 self, body: list[Table], name: str | None = None, parsed: bool = False
1905 ) -> None:
1906 self.name = name
1907 self._body: list[Table] = []
1908 self._parsed = parsed
1910 super().__init__(Trivia(trail=""))
1912 for table in body:
1913 self.append(table)
1915 def unwrap(self) -> list[dict[str, Any]]:
1916 unwrapped = []
1917 for t in self._body:
1918 if hasattr(t, "unwrap"):
1919 unwrapped.append(t.unwrap())
1920 else:
1921 unwrapped.append(t)
1922 return unwrapped
1924 @property
1925 def body(self) -> list[Table]:
1926 return self._body
1928 @property
1929 def discriminant(self) -> int:
1930 return 12
1932 @property
1933 def value(self) -> list[dict[Any, Any]]:
1934 return [v.value for v in self._body]
1936 def __len__(self) -> int:
1937 return len(self._body)
1939 @overload
1940 def __getitem__(self, key: slice) -> list[Table]: ...
1942 @overload
1943 def __getitem__(self, key: int) -> Table: ...
1945 def __getitem__(self, key):
1946 return self._body[key]
1948 def __setitem__(self, key: slice | int, value: Any) -> None:
1949 self._body[key] = item(value, _parent=self)
1951 def __delitem__(self, key: slice | int) -> None:
1952 del self._body[key]
1953 list.__delitem__(self, key)
1955 def insert(self, index: int, value: dict) -> None:
1956 value = item(value, _parent=self)
1957 if not isinstance(value, Table):
1958 raise ValueError(f"Unsupported insert value type: {type(value)}")
1959 length = len(self)
1960 if index < 0:
1961 index += length
1962 if index < 0:
1963 index = 0
1964 elif index >= length:
1965 index = length
1966 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1967 if m:
1968 indent = m.group(1)
1970 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1971 if not m:
1972 value.trivia.indent = indent
1973 else:
1974 value.trivia.indent = m.group(1) + indent + m.group(2)
1975 prev_table = self._body[index - 1] if 0 < index and length else None
1976 next_table = self._body[index + 1] if index < length - 1 else None
1977 if not self._parsed:
1978 if prev_table and "\n" not in value.trivia.indent:
1979 value.trivia.indent = "\n" + value.trivia.indent
1980 if next_table and "\n" not in next_table.trivia.indent:
1981 next_table.trivia.indent = "\n" + next_table.trivia.indent
1982 self._body.insert(index, value)
1983 list.insert(self, index, value)
1985 def invalidate_display_name(self):
1986 """Call ``invalidate_display_name`` on the contained tables"""
1987 for child in self:
1988 if hasattr(child, "invalidate_display_name"):
1989 child.invalidate_display_name()
1991 def as_string(self) -> str:
1992 b = ""
1993 for table in self._body:
1994 b += table.as_string()
1996 return b
1998 def __repr__(self) -> str:
1999 return f"<AoT {self.value}>"
2001 def _getstate(self, protocol=3):
2002 return self._body, self.name, self._parsed
2005class Null(Item):
2006 """
2007 A null item.
2008 """
2010 def __init__(self) -> None:
2011 super().__init__(Trivia(trail=""))
2013 def unwrap(self) -> None:
2014 return None
2016 @property
2017 def discriminant(self) -> int:
2018 return -1
2020 @property
2021 def value(self) -> None:
2022 return None
2024 def as_string(self) -> str:
2025 return ""
2027 def _getstate(self, protocol=3) -> tuple:
2028 return ()