Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/items.py: 68%
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 re
8import string
10from collections.abc import Collection
11from collections.abc import Iterable
12from collections.abc import Iterator
13from collections.abc import Sequence
14from datetime import date
15from datetime import datetime
16from datetime import time
17from datetime import tzinfo
18from enum import Enum
19from typing import TYPE_CHECKING
20from typing import Any
21from typing import TypeVar
22from typing import cast
23from typing import overload
25from tomlkit._compat import PY38
26from tomlkit._compat import decode
27from tomlkit._types import _CustomDict
28from tomlkit._types import _CustomFloat
29from tomlkit._types import _CustomInt
30from tomlkit._types import _CustomList
31from tomlkit._types import wrap_method
32from tomlkit._utils import CONTROL_CHARS
33from tomlkit._utils import escape_string
34from tomlkit.exceptions import ConvertError
35from tomlkit.exceptions import InvalidStringError
38if TYPE_CHECKING:
39 from typing import Protocol
41 from tomlkit import container
43 class Encoder(Protocol):
44 def __call__(
45 self, __value: Any, _parent: Item | None = None, _sort_keys: bool = False
46 ) -> Item: ...
49ItemT = TypeVar("ItemT", bound="Item")
50CUSTOM_ENCODERS: list[Encoder] = []
51AT = TypeVar("AT", bound="AbstractTable")
54@overload
55def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ...
58@overload
59def item(value: int, _parent: Item | None = ..., _sort_keys: bool = ...) -> Integer: ...
62@overload
63def item(value: float, _parent: Item | None = ..., _sort_keys: bool = ...) -> Float: ...
66@overload
67def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> String: ...
70@overload
71def item(
72 value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...
73) -> DateTime: ...
76@overload
77def item(value: date, _parent: Item | None = ..., _sort_keys: bool = ...) -> Date: ...
80@overload
81def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Time: ...
84@overload
85def item(
86 value: Sequence[dict], _parent: Item | None = ..., _sort_keys: bool = ...
87) -> AoT: ...
90@overload
91def item(
92 value: Sequence, _parent: Item | None = ..., _sort_keys: bool = ...
93) -> Array: ...
96@overload
97def item(value: dict, _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ...
100@overload
101def item(value: dict, _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ...
104@overload
105def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ...
108def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item:
109 """Create a TOML item from a Python object.
111 :Example:
113 >>> item(42)
114 42
115 >>> item([1, 2, 3])
116 [1, 2, 3]
117 >>> item({'a': 1, 'b': 2})
118 a = 1
119 b = 2
120 """
122 from tomlkit.container import Container
124 if isinstance(value, Item):
125 return value
127 if isinstance(value, bool):
128 return Bool(value, Trivia())
129 elif isinstance(value, int):
130 return Integer(value, Trivia(), str(value))
131 elif isinstance(value, float):
132 return Float(value, Trivia(), str(value))
133 elif isinstance(value, dict):
134 table_constructor = (
135 InlineTable if isinstance(_parent, (Array, InlineTable)) else Table
136 )
137 val = table_constructor(Container(), Trivia(), False)
138 for k, v in sorted(
139 value.items(),
140 key=lambda i: (isinstance(i[1], dict), i[0]) if _sort_keys else 1,
141 ):
142 val[k] = item(v, _parent=val, _sort_keys=_sort_keys)
144 return val
145 elif isinstance(value, (list, tuple)):
146 if (
147 value
148 and all(isinstance(v, dict) for v in value)
149 and (_parent is None or isinstance(_parent, Table))
150 ):
151 a = AoT([])
152 table_constructor = Table
153 else:
154 a = Array([], Trivia())
155 table_constructor = InlineTable
157 for v in value:
158 if isinstance(v, dict):
159 table = table_constructor(Container(), Trivia(), True)
161 for k, _v in sorted(
162 v.items(),
163 key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
164 ):
165 i = item(_v, _parent=table, _sort_keys=_sort_keys)
166 if isinstance(table, InlineTable):
167 i.trivia.trail = ""
169 table[k] = i
171 v = table
173 a.append(v)
175 return a
176 elif isinstance(value, str):
177 return String.from_raw(value)
178 elif isinstance(value, datetime):
179 return DateTime(
180 value.year,
181 value.month,
182 value.day,
183 value.hour,
184 value.minute,
185 value.second,
186 value.microsecond,
187 value.tzinfo,
188 Trivia(),
189 value.isoformat().replace("+00:00", "Z"),
190 )
191 elif isinstance(value, date):
192 return Date(value.year, value.month, value.day, Trivia(), value.isoformat())
193 elif isinstance(value, time):
194 return Time(
195 value.hour,
196 value.minute,
197 value.second,
198 value.microsecond,
199 value.tzinfo,
200 Trivia(),
201 value.isoformat(),
202 )
203 else:
204 for encoder in CUSTOM_ENCODERS:
205 try:
206 # Check if encoder accepts keyword arguments for backward compatibility
207 sig = inspect.signature(encoder)
208 if "_parent" in sig.parameters or any(
209 p.kind == p.VAR_KEYWORD for p in sig.parameters.values()
210 ):
211 # New style encoder that can accept additional parameters
212 rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys)
213 else:
214 # Old style encoder that only accepts value
215 rv = encoder(value)
216 except ConvertError:
217 pass
218 else:
219 if not isinstance(rv, Item):
220 raise ConvertError(
221 f"Custom encoder is expected to return an instance of Item, got {type(rv)}"
222 )
223 return rv
225 raise ConvertError(f"Unable to convert an object of {type(value)} to a TOML item")
228class StringType(Enum):
229 # Single Line Basic
230 SLB = '"'
231 # Multi Line Basic
232 MLB = '"""'
233 # Single Line Literal
234 SLL = "'"
235 # Multi Line Literal
236 MLL = "'''"
238 @classmethod
239 def select(cls, literal=False, multiline=False) -> StringType:
240 return {
241 (False, False): cls.SLB,
242 (False, True): cls.MLB,
243 (True, False): cls.SLL,
244 (True, True): cls.MLL,
245 }[(literal, multiline)]
247 @property
248 def escaped_sequences(self) -> Collection[str]:
249 # https://toml.io/en/v1.0.0#string
250 escaped_in_basic = CONTROL_CHARS | {"\\"}
251 allowed_in_multiline = {"\n", "\r"}
252 return {
253 StringType.SLB: escaped_in_basic | {'"'},
254 StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline,
255 StringType.SLL: (),
256 StringType.MLL: (),
257 }[self]
259 @property
260 def invalid_sequences(self) -> Collection[str]:
261 # https://toml.io/en/v1.0.0#string
262 forbidden_in_literal = CONTROL_CHARS - {"\t"}
263 allowed_in_multiline = {"\n", "\r"}
264 return {
265 StringType.SLB: (),
266 StringType.MLB: (),
267 StringType.SLL: forbidden_in_literal | {"'"},
268 StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline,
269 }[self]
271 @property
272 def unit(self) -> str:
273 return self.value[0]
275 def is_basic(self) -> bool:
276 return self in {StringType.SLB, StringType.MLB}
278 def is_literal(self) -> bool:
279 return self in {StringType.SLL, StringType.MLL}
281 def is_singleline(self) -> bool:
282 return self in {StringType.SLB, StringType.SLL}
284 def is_multiline(self) -> bool:
285 return self in {StringType.MLB, StringType.MLL}
287 def toggle(self) -> StringType:
288 return {
289 StringType.SLB: StringType.MLB,
290 StringType.MLB: StringType.SLB,
291 StringType.SLL: StringType.MLL,
292 StringType.MLL: StringType.SLL,
293 }[self]
296class BoolType(Enum):
297 TRUE = "true"
298 FALSE = "false"
300 def __bool__(self):
301 return {BoolType.TRUE: True, BoolType.FALSE: False}[self]
303 def __iter__(self):
304 return iter(self.value)
306 def __len__(self):
307 return len(self.value)
310@dataclasses.dataclass
311class Trivia:
312 """
313 Trivia information (aka metadata).
314 """
316 # Whitespace before a value.
317 indent: str = ""
318 # Whitespace after a value, but before a comment.
319 comment_ws: str = ""
320 # Comment, starting with # character, or empty string if no comment.
321 comment: str = ""
322 # Trailing newline.
323 trail: str = "\n"
325 def copy(self) -> Trivia:
326 return dataclasses.replace(self)
329class KeyType(Enum):
330 """
331 The type of a Key.
333 Keys can be bare (unquoted), or quoted using basic ("), or literal (')
334 quotes following the same escaping rules as single-line StringType.
335 """
337 Bare = ""
338 Basic = '"'
339 Literal = "'"
342class Key(abc.ABC):
343 """Base class for a key"""
345 sep: str
346 _original: str
347 _keys: list[SingleKey]
348 _dotted: bool
349 key: str
351 @abc.abstractmethod
352 def __hash__(self) -> int:
353 pass
355 @abc.abstractmethod
356 def __eq__(self, __o: object) -> bool:
357 pass
359 def is_dotted(self) -> bool:
360 """If the key is followed by other keys"""
361 return self._dotted
363 def __iter__(self) -> Iterator[SingleKey]:
364 return iter(self._keys)
366 def concat(self, other: Key) -> DottedKey:
367 """Concatenate keys into a dotted key"""
368 keys = self._keys + other._keys
369 return DottedKey(keys, sep=self.sep)
371 def is_multi(self) -> bool:
372 """Check if the key contains multiple keys"""
373 return len(self._keys) > 1
375 def as_string(self) -> str:
376 """The TOML representation"""
377 return self._original
379 def __str__(self) -> str:
380 return self.as_string()
382 def __repr__(self) -> str:
383 return f"<Key {self.as_string()}>"
386class SingleKey(Key):
387 """A single key"""
389 def __init__(
390 self,
391 k: str,
392 t: KeyType | None = None,
393 sep: str | None = None,
394 original: str | None = None,
395 ) -> None:
396 if not isinstance(k, str):
397 raise TypeError("Keys must be strings")
399 if t is None:
400 if not k or any(
401 c not in string.ascii_letters + string.digits + "-" + "_" for c in k
402 ):
403 t = KeyType.Basic
404 else:
405 t = KeyType.Bare
407 self.t = t
408 if sep is None:
409 sep = " = "
411 self.sep = sep
412 self.key = k
413 if original is None:
414 key_str = escape_string(k) if t == KeyType.Basic else k
415 original = f"{t.value}{key_str}{t.value}"
417 self._original = original
418 self._keys = [self]
419 self._dotted = False
421 @property
422 def delimiter(self) -> str:
423 """The delimiter: double quote/single quote/none"""
424 return self.t.value
426 def is_bare(self) -> bool:
427 """Check if the key is bare"""
428 return self.t == KeyType.Bare
430 def __hash__(self) -> int:
431 return hash(self.key)
433 def __eq__(self, other: Any) -> bool:
434 if isinstance(other, Key):
435 return isinstance(other, SingleKey) and self.key == other.key
437 return self.key == other
440class DottedKey(Key):
441 def __init__(
442 self,
443 keys: Iterable[SingleKey],
444 sep: str | None = None,
445 original: str | None = None,
446 ) -> None:
447 self._keys = list(keys)
448 if original is None:
449 original = ".".join(k.as_string() for k in self._keys)
451 self.sep = " = " if sep is None else sep
452 self._original = original
453 self._dotted = False
454 self.key = ".".join(k.key for k in self._keys)
456 def __hash__(self) -> int:
457 return hash(tuple(self._keys))
459 def __eq__(self, __o: object) -> bool:
460 return isinstance(__o, DottedKey) and self._keys == __o._keys
463class Item:
464 """
465 An item within a TOML document.
466 """
468 def __init__(self, trivia: Trivia) -> None:
469 self._trivia = trivia
471 @property
472 def trivia(self) -> Trivia:
473 """The trivia element associated with this item"""
474 return self._trivia
476 @property
477 def discriminant(self) -> int:
478 raise NotImplementedError()
480 def as_string(self) -> str:
481 """The TOML representation"""
482 raise NotImplementedError()
484 @property
485 def value(self) -> Any:
486 return self
488 def unwrap(self) -> Any:
489 """Returns as pure python object (ppo)"""
490 raise NotImplementedError()
492 # Helpers
494 def comment(self, comment: str) -> Item:
495 """Attach a comment to this item"""
496 if not comment.strip().startswith("#"):
497 comment = "# " + comment
499 self._trivia.comment_ws = " "
500 self._trivia.comment = comment
502 return self
504 def indent(self, indent: int) -> Item:
505 """Indent this item with given number of spaces"""
506 if self._trivia.indent.startswith("\n"):
507 self._trivia.indent = "\n" + " " * indent
508 else:
509 self._trivia.indent = " " * indent
511 return self
513 def is_boolean(self) -> bool:
514 return isinstance(self, Bool)
516 def is_table(self) -> bool:
517 return isinstance(self, Table)
519 def is_inline_table(self) -> bool:
520 return isinstance(self, InlineTable)
522 def is_aot(self) -> bool:
523 return isinstance(self, AoT)
525 def _getstate(self, protocol=3):
526 return (self._trivia,)
528 def __reduce__(self):
529 return self.__reduce_ex__(2)
531 def __reduce_ex__(self, protocol):
532 return self.__class__, self._getstate(protocol)
535class Whitespace(Item):
536 """
537 A whitespace literal.
538 """
540 def __init__(self, s: str, fixed: bool = False) -> None:
541 self._s = s
542 self._fixed = fixed
544 @property
545 def s(self) -> str:
546 return self._s
548 @property
549 def value(self) -> str:
550 """The wrapped string of the whitespace"""
551 return self._s
553 @property
554 def trivia(self) -> Trivia:
555 raise RuntimeError("Called trivia on a Whitespace variant.")
557 @property
558 def discriminant(self) -> int:
559 return 0
561 def is_fixed(self) -> bool:
562 """If the whitespace is fixed, it can't be merged or discarded from the output."""
563 return self._fixed
565 def as_string(self) -> str:
566 return self._s
568 def __repr__(self) -> str:
569 return f"<{self.__class__.__name__} {self._s!r}>"
571 def _getstate(self, protocol=3):
572 return self._s, self._fixed
575class Comment(Item):
576 """
577 A comment literal.
578 """
580 @property
581 def discriminant(self) -> int:
582 return 1
584 def as_string(self) -> str:
585 return (
586 f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
587 )
589 def __str__(self) -> str:
590 return f"{self._trivia.indent}{decode(self._trivia.comment)}"
593class Integer(Item, _CustomInt):
594 """
595 An integer literal.
596 """
598 def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
599 return int.__new__(cls, value)
601 def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
602 super().__init__(trivia)
603 self._original = value
604 self._raw = raw
605 self._sign = False
607 if re.match(r"^[+\-]\d+$", raw):
608 self._sign = True
610 def unwrap(self) -> int:
611 return self._original
613 __int__ = unwrap
615 def __hash__(self) -> int:
616 return hash(self.unwrap())
618 @property
619 def discriminant(self) -> int:
620 return 2
622 @property
623 def value(self) -> int:
624 """The wrapped integer value"""
625 return self
627 def as_string(self) -> str:
628 return self._raw
630 def _new(self, result):
631 raw = str(result)
632 if self._sign and result >= 0:
633 raw = f"+{raw}"
635 return Integer(result, self._trivia, raw)
637 def _getstate(self, protocol=3):
638 return int(self), self._trivia, self._raw
640 # int methods
641 __abs__ = wrap_method(int.__abs__)
642 __add__ = wrap_method(int.__add__)
643 __and__ = wrap_method(int.__and__)
644 __ceil__ = wrap_method(int.__ceil__)
645 __eq__ = int.__eq__
646 __floor__ = wrap_method(int.__floor__)
647 __floordiv__ = wrap_method(int.__floordiv__)
648 __invert__ = wrap_method(int.__invert__)
649 __le__ = int.__le__
650 __lshift__ = wrap_method(int.__lshift__)
651 __lt__ = int.__lt__
652 __mod__ = wrap_method(int.__mod__)
653 __mul__ = wrap_method(int.__mul__)
654 __neg__ = wrap_method(int.__neg__)
655 __or__ = wrap_method(int.__or__)
656 __pos__ = wrap_method(int.__pos__)
657 __pow__ = wrap_method(int.__pow__)
658 __radd__ = wrap_method(int.__radd__)
659 __rand__ = wrap_method(int.__rand__)
660 __rfloordiv__ = wrap_method(int.__rfloordiv__)
661 __rlshift__ = wrap_method(int.__rlshift__)
662 __rmod__ = wrap_method(int.__rmod__)
663 __rmul__ = wrap_method(int.__rmul__)
664 __ror__ = wrap_method(int.__ror__)
665 __round__ = wrap_method(int.__round__)
666 __rpow__ = wrap_method(int.__rpow__)
667 __rrshift__ = wrap_method(int.__rrshift__)
668 __rshift__ = wrap_method(int.__rshift__)
669 __rxor__ = wrap_method(int.__rxor__)
670 __trunc__ = wrap_method(int.__trunc__)
671 __xor__ = wrap_method(int.__xor__)
673 def __rtruediv__(self, other):
674 result = int.__rtruediv__(self, other)
675 if result is NotImplemented:
676 return result
677 return Float._new(self, result)
679 def __truediv__(self, other):
680 result = int.__truediv__(self, other)
681 if result is NotImplemented:
682 return result
683 return Float._new(self, result)
686class Float(Item, _CustomFloat):
687 """
688 A float literal.
689 """
691 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
692 return float.__new__(cls, value)
694 def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
695 super().__init__(trivia)
696 self._original = value
697 self._raw = raw
698 self._sign = False
700 if re.match(r"^[+\-].+$", raw):
701 self._sign = True
703 def unwrap(self) -> float:
704 return self._original
706 __float__ = unwrap
708 def __hash__(self) -> int:
709 return hash(self.unwrap())
711 @property
712 def discriminant(self) -> int:
713 return 3
715 @property
716 def value(self) -> float:
717 """The wrapped float value"""
718 return self
720 def as_string(self) -> str:
721 return self._raw
723 def _new(self, result):
724 raw = str(result)
726 if self._sign and result >= 0:
727 raw = f"+{raw}"
729 return Float(result, self._trivia, raw)
731 def _getstate(self, protocol=3):
732 return float(self), self._trivia, self._raw
734 # float methods
735 __abs__ = wrap_method(float.__abs__)
736 __add__ = wrap_method(float.__add__)
737 __eq__ = float.__eq__
738 __floordiv__ = wrap_method(float.__floordiv__)
739 __le__ = float.__le__
740 __lt__ = float.__lt__
741 __mod__ = wrap_method(float.__mod__)
742 __mul__ = wrap_method(float.__mul__)
743 __neg__ = wrap_method(float.__neg__)
744 __pos__ = wrap_method(float.__pos__)
745 __pow__ = wrap_method(float.__pow__)
746 __radd__ = wrap_method(float.__radd__)
747 __rfloordiv__ = wrap_method(float.__rfloordiv__)
748 __rmod__ = wrap_method(float.__rmod__)
749 __rmul__ = wrap_method(float.__rmul__)
750 __round__ = wrap_method(float.__round__)
751 __rpow__ = wrap_method(float.__rpow__)
752 __rtruediv__ = wrap_method(float.__rtruediv__)
753 __truediv__ = wrap_method(float.__truediv__)
754 __trunc__ = float.__trunc__
756 __ceil__ = float.__ceil__
757 __floor__ = float.__floor__
760class Bool(Item):
761 """
762 A boolean literal.
763 """
765 def __init__(self, t: int, trivia: Trivia) -> None:
766 super().__init__(trivia)
768 self._value = bool(t)
770 def unwrap(self) -> bool:
771 return bool(self)
773 @property
774 def discriminant(self) -> int:
775 return 4
777 @property
778 def value(self) -> bool:
779 """The wrapped boolean value"""
780 return self._value
782 def as_string(self) -> str:
783 return str(self._value).lower()
785 def _getstate(self, protocol=3):
786 return self._value, self._trivia
788 def __bool__(self):
789 return self._value
791 __nonzero__ = __bool__
793 def __eq__(self, other):
794 if not isinstance(other, bool):
795 return NotImplemented
797 return other == self._value
799 def __hash__(self):
800 return hash(self._value)
802 def __repr__(self):
803 return repr(self._value)
806class DateTime(Item, datetime):
807 """
808 A datetime literal.
809 """
811 def __new__(
812 cls,
813 year: int,
814 month: int,
815 day: int,
816 hour: int,
817 minute: int,
818 second: int,
819 microsecond: int,
820 tzinfo: tzinfo | None,
821 *_: Any,
822 **kwargs: Any,
823 ) -> datetime:
824 return datetime.__new__(
825 cls,
826 year,
827 month,
828 day,
829 hour,
830 minute,
831 second,
832 microsecond,
833 tzinfo=tzinfo,
834 **kwargs,
835 )
837 def __init__(
838 self,
839 year: int,
840 month: int,
841 day: int,
842 hour: int,
843 minute: int,
844 second: int,
845 microsecond: int,
846 tzinfo: tzinfo | None,
847 trivia: Trivia | None = None,
848 raw: str | None = None,
849 **kwargs: Any,
850 ) -> None:
851 super().__init__(trivia or Trivia())
853 self._raw = raw or self.isoformat()
855 def unwrap(self) -> datetime:
856 (
857 year,
858 month,
859 day,
860 hour,
861 minute,
862 second,
863 microsecond,
864 tzinfo,
865 _,
866 _,
867 ) = self._getstate()
868 return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
870 @property
871 def discriminant(self) -> int:
872 return 5
874 @property
875 def value(self) -> datetime:
876 return self
878 def as_string(self) -> str:
879 return self._raw
881 def __add__(self, other):
882 if PY38:
883 result = datetime(
884 self.year,
885 self.month,
886 self.day,
887 self.hour,
888 self.minute,
889 self.second,
890 self.microsecond,
891 self.tzinfo,
892 ).__add__(other)
893 else:
894 result = super().__add__(other)
896 return self._new(result)
898 def __sub__(self, other):
899 if PY38:
900 result = datetime(
901 self.year,
902 self.month,
903 self.day,
904 self.hour,
905 self.minute,
906 self.second,
907 self.microsecond,
908 self.tzinfo,
909 ).__sub__(other)
910 else:
911 result = super().__sub__(other)
913 if isinstance(result, datetime):
914 result = self._new(result)
916 return result
918 def replace(self, *args: Any, **kwargs: Any) -> datetime:
919 return self._new(super().replace(*args, **kwargs))
921 def astimezone(self, tz: tzinfo) -> datetime:
922 result = super().astimezone(tz)
923 if PY38:
924 return result
925 return self._new(result)
927 def _new(self, result) -> DateTime:
928 raw = result.isoformat()
930 return DateTime(
931 result.year,
932 result.month,
933 result.day,
934 result.hour,
935 result.minute,
936 result.second,
937 result.microsecond,
938 result.tzinfo,
939 self._trivia,
940 raw,
941 )
943 def _getstate(self, protocol=3):
944 return (
945 self.year,
946 self.month,
947 self.day,
948 self.hour,
949 self.minute,
950 self.second,
951 self.microsecond,
952 self.tzinfo,
953 self._trivia,
954 self._raw,
955 )
958class Date(Item, date):
959 """
960 A date literal.
961 """
963 def __new__(cls, year: int, month: int, day: int, *_: Any) -> date:
964 return date.__new__(cls, year, month, day)
966 def __init__(
967 self,
968 year: int,
969 month: int,
970 day: int,
971 trivia: Trivia | None = None,
972 raw: str = "",
973 ) -> None:
974 super().__init__(trivia or Trivia())
976 self._raw = raw
978 def unwrap(self) -> date:
979 (year, month, day, _, _) = self._getstate()
980 return date(year, month, day)
982 @property
983 def discriminant(self) -> int:
984 return 6
986 @property
987 def value(self) -> date:
988 return self
990 def as_string(self) -> str:
991 return self._raw
993 def __add__(self, other):
994 if PY38:
995 result = date(self.year, self.month, self.day).__add__(other)
996 else:
997 result = super().__add__(other)
999 return self._new(result)
1001 def __sub__(self, other):
1002 if PY38:
1003 result = date(self.year, self.month, self.day).__sub__(other)
1004 else:
1005 result = super().__sub__(other)
1007 if isinstance(result, date):
1008 result = self._new(result)
1010 return result
1012 def replace(self, *args: Any, **kwargs: Any) -> date:
1013 return self._new(super().replace(*args, **kwargs))
1015 def _new(self, result):
1016 raw = result.isoformat()
1018 return Date(result.year, result.month, result.day, self._trivia, raw)
1020 def _getstate(self, protocol=3):
1021 return (self.year, self.month, self.day, self._trivia, self._raw)
1024class Time(Item, time):
1025 """
1026 A time literal.
1027 """
1029 def __new__(
1030 cls,
1031 hour: int,
1032 minute: int,
1033 second: int,
1034 microsecond: int,
1035 tzinfo: tzinfo | None,
1036 *_: Any,
1037 ) -> time:
1038 return time.__new__(cls, hour, minute, second, microsecond, tzinfo)
1040 def __init__(
1041 self,
1042 hour: int,
1043 minute: int,
1044 second: int,
1045 microsecond: int,
1046 tzinfo: tzinfo | None,
1047 trivia: Trivia | None = None,
1048 raw: str = "",
1049 ) -> None:
1050 super().__init__(trivia or Trivia())
1052 self._raw = raw
1054 def unwrap(self) -> time:
1055 (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
1056 return time(hour, minute, second, microsecond, tzinfo)
1058 @property
1059 def discriminant(self) -> int:
1060 return 7
1062 @property
1063 def value(self) -> time:
1064 return self
1066 def as_string(self) -> str:
1067 return self._raw
1069 def replace(self, *args: Any, **kwargs: Any) -> time:
1070 return self._new(super().replace(*args, **kwargs))
1072 def _new(self, result):
1073 raw = result.isoformat()
1075 return Time(
1076 result.hour,
1077 result.minute,
1078 result.second,
1079 result.microsecond,
1080 result.tzinfo,
1081 self._trivia,
1082 raw,
1083 )
1085 def _getstate(self, protocol: int = 3) -> tuple:
1086 return (
1087 self.hour,
1088 self.minute,
1089 self.second,
1090 self.microsecond,
1091 self.tzinfo,
1092 self._trivia,
1093 self._raw,
1094 )
1097class _ArrayItemGroup:
1098 __slots__ = ("comma", "comment", "indent", "value")
1100 def __init__(
1101 self,
1102 value: Item | None = None,
1103 indent: Whitespace | None = None,
1104 comma: Whitespace | None = None,
1105 comment: Comment | None = None,
1106 ) -> None:
1107 self.value = value
1108 self.indent = indent
1109 self.comma = comma
1110 self.comment = comment
1112 def __iter__(self) -> Iterator[Item]:
1113 return filter(
1114 lambda x: x is not None, (self.indent, self.value, self.comma, self.comment)
1115 )
1117 def __repr__(self) -> str:
1118 return repr(tuple(self))
1120 def is_whitespace(self) -> bool:
1121 return self.value is None and self.comment is None
1123 def __bool__(self) -> bool:
1124 try:
1125 next(iter(self))
1126 except StopIteration:
1127 return False
1128 return True
1131class Array(Item, _CustomList):
1132 """
1133 An array literal
1134 """
1136 def __init__(
1137 self, value: list[Item], trivia: Trivia, multiline: bool = False
1138 ) -> None:
1139 super().__init__(trivia)
1140 list.__init__(
1141 self,
1142 [v for v in value if not isinstance(v, (Whitespace, Comment, Null))],
1143 )
1144 self._index_map: dict[int, int] = {}
1145 self._value = self._group_values(value)
1146 self._multiline = multiline
1147 self._reindex()
1149 def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
1150 """Group the values into (indent, value, comma, comment) tuples"""
1151 groups = []
1152 this_group = _ArrayItemGroup()
1153 start_new_group = False
1154 for item in value:
1155 if isinstance(item, Whitespace):
1156 if "," not in item.s or start_new_group:
1157 groups.append(this_group)
1158 this_group = _ArrayItemGroup(indent=item)
1159 start_new_group = False
1160 else:
1161 if this_group.value is None:
1162 # when comma is met and no value is provided, add a dummy Null
1163 this_group.value = Null()
1164 this_group.comma = item
1165 elif isinstance(item, Comment):
1166 if this_group.value is None:
1167 this_group.value = Null()
1168 this_group.comment = item
1169 # Comments are the last item in a group.
1170 start_new_group = True
1171 elif this_group.value is None:
1172 this_group.value = item
1173 else:
1174 groups.append(this_group)
1175 this_group = _ArrayItemGroup(value=item)
1176 groups.append(this_group)
1177 return [group for group in groups if group]
1179 def unwrap(self) -> list[Any]:
1180 unwrapped = []
1181 for v in self:
1182 if hasattr(v, "unwrap"):
1183 unwrapped.append(v.unwrap())
1184 else:
1185 unwrapped.append(v)
1186 return unwrapped
1188 @property
1189 def discriminant(self) -> int:
1190 return 8
1192 @property
1193 def value(self) -> list:
1194 return self
1196 def _iter_items(self) -> Iterator[Item]:
1197 for v in self._value:
1198 yield from v
1200 def multiline(self, multiline: bool) -> Array:
1201 """Change the array to display in multiline or not.
1203 :Example:
1205 >>> a = item([1, 2, 3])
1206 >>> print(a.as_string())
1207 [1, 2, 3]
1208 >>> print(a.multiline(True).as_string())
1209 [
1210 1,
1211 2,
1212 3,
1213 ]
1214 """
1215 self._multiline = multiline
1217 return self
1219 def as_string(self) -> str:
1220 if not self._multiline or not self._value:
1221 return f"[{''.join(v.as_string() for v in self._iter_items())}]"
1223 s = "[\n"
1224 s += "".join(
1225 self.trivia.indent
1226 + " " * 4
1227 + v.value.as_string()
1228 + ("," if not isinstance(v.value, Null) else "")
1229 + (v.comment.as_string() if v.comment is not None else "")
1230 + "\n"
1231 for v in self._value
1232 if v.value is not None
1233 )
1234 s += self.trivia.indent + "]"
1236 return s
1238 def _reindex(self) -> None:
1239 self._index_map.clear()
1240 index = 0
1241 for i, v in enumerate(self._value):
1242 if v.value is None or isinstance(v.value, Null):
1243 continue
1244 self._index_map[index] = i
1245 index += 1
1247 def add_line(
1248 self,
1249 *items: Any,
1250 indent: str = " ",
1251 comment: str | None = None,
1252 add_comma: bool = True,
1253 newline: bool = True,
1254 ) -> None:
1255 """Add multiple items in a line to control the format precisely.
1256 When add_comma is True, only accept actual values and
1257 ", " will be added between values automatically.
1259 :Example:
1261 >>> a = array()
1262 >>> a.add_line(1, 2, 3)
1263 >>> a.add_line(4, 5, 6)
1264 >>> a.add_line(indent="")
1265 >>> print(a.as_string())
1266 [
1267 1, 2, 3,
1268 4, 5, 6,
1269 ]
1270 """
1271 new_values: list[Item] = []
1272 first_indent = f"\n{indent}" if newline else indent
1273 if first_indent:
1274 new_values.append(Whitespace(first_indent))
1275 whitespace = ""
1276 data_values = []
1277 for i, el in enumerate(items):
1278 it = item(el, _parent=self)
1279 if isinstance(it, Comment) or (add_comma and isinstance(el, Whitespace)):
1280 raise ValueError(f"item type {type(it)} is not allowed in add_line")
1281 if not isinstance(it, Whitespace):
1282 if whitespace:
1283 new_values.append(Whitespace(whitespace))
1284 whitespace = ""
1285 new_values.append(it)
1286 data_values.append(it.value)
1287 if add_comma:
1288 new_values.append(Whitespace(","))
1289 if i != len(items) - 1:
1290 new_values.append(Whitespace(" "))
1291 elif "," not in it.s:
1292 whitespace += it.s
1293 else:
1294 new_values.append(it)
1295 if whitespace:
1296 new_values.append(Whitespace(whitespace))
1297 if comment:
1298 indent = " " if items else ""
1299 new_values.append(
1300 Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
1301 )
1302 list.extend(self, data_values)
1303 if len(self._value) > 0:
1304 last_item = self._value[-1]
1305 last_value_item = next(
1306 (
1307 v
1308 for v in self._value[::-1]
1309 if v.value is not None and not isinstance(v.value, Null)
1310 ),
1311 None,
1312 )
1313 if last_value_item is not None:
1314 last_value_item.comma = Whitespace(",")
1315 if last_item.is_whitespace():
1316 self._value[-1:-1] = self._group_values(new_values)
1317 else:
1318 self._value.extend(self._group_values(new_values))
1319 else:
1320 self._value.extend(self._group_values(new_values))
1321 self._reindex()
1323 def clear(self) -> None:
1324 """Clear the array."""
1325 list.clear(self)
1326 self._index_map.clear()
1327 self._value.clear()
1329 def __len__(self) -> int:
1330 return list.__len__(self)
1332 def item(self, index: int) -> Item:
1333 rv = list.__getitem__(self, index)
1334 return cast(Item, rv)
1336 def __getitem__(self, key: int | slice) -> Any:
1337 rv = list.__getitem__(self, key)
1338 if isinstance(rv, Bool):
1339 return rv.value
1340 return rv
1342 def __setitem__(self, key: int | slice, value: Any) -> Any:
1343 it = item(value, _parent=self)
1344 list.__setitem__(self, key, it)
1345 if isinstance(key, slice):
1346 raise ValueError("slice assignment is not supported")
1347 if key < 0:
1348 key += len(self)
1349 self._value[self._index_map[key]].value = it
1351 def insert(self, pos: int, value: Any) -> None:
1352 it = item(value, _parent=self)
1353 length = len(self)
1354 if not isinstance(it, (Comment, Whitespace)):
1355 list.insert(self, pos, it)
1356 if pos < 0:
1357 pos += length
1358 if pos < 0:
1359 pos = 0
1361 idx = 0 # insert position of the self._value list
1362 default_indent = " "
1363 if pos < length:
1364 try:
1365 idx = self._index_map[pos]
1366 except KeyError as e:
1367 raise IndexError("list index out of range") from e
1368 else:
1369 idx = len(self._value)
1370 if idx >= 1 and self._value[idx - 1].is_whitespace():
1371 # The last item is a pure whitespace(\n ), insert before it
1372 idx -= 1
1373 if (
1374 self._value[idx].indent is not None
1375 and "\n" in self._value[idx].indent.s
1376 ):
1377 default_indent = "\n "
1378 indent: Item | None = None
1379 comma: Item | None = Whitespace(",") if pos < length else None
1380 if idx < len(self._value) and not self._value[idx].is_whitespace():
1381 # Prefer to copy the indentation from the item after
1382 indent = self._value[idx].indent
1383 if idx > 0:
1384 last_item = self._value[idx - 1]
1385 if indent is None:
1386 indent = last_item.indent
1387 if not isinstance(last_item.value, Null) and "\n" in default_indent:
1388 # Copy the comma from the last item if 1) it contains a value and
1389 # 2) the array is multiline
1390 comma = last_item.comma
1391 if last_item.comma is None and not isinstance(last_item.value, Null):
1392 # Add comma to the last item to separate it from the following items.
1393 last_item.comma = Whitespace(",")
1394 if indent is None and (idx > 0 or "\n" in default_indent):
1395 # apply default indent if it isn't the first item or the array is multiline.
1396 indent = Whitespace(default_indent)
1397 new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
1398 self._value.insert(idx, new_item)
1399 self._reindex()
1401 def __delitem__(self, key: int | slice):
1402 length = len(self)
1403 list.__delitem__(self, key)
1405 if isinstance(key, slice):
1406 indices_to_remove = list(
1407 range(key.start or 0, key.stop or length, key.step or 1)
1408 )
1409 else:
1410 indices_to_remove = [length + key if key < 0 else key]
1411 for i in sorted(indices_to_remove, reverse=True):
1412 try:
1413 idx = self._index_map[i]
1414 except KeyError as e:
1415 if not isinstance(key, slice):
1416 raise IndexError("list index out of range") from e
1417 else:
1418 group_rm = self._value[idx]
1419 del self._value[idx]
1420 if (
1421 idx == 0
1422 and len(self._value) > 0
1423 and self._value[idx].indent
1424 and "\n" not in self._value[idx].indent.s
1425 ):
1426 # Remove the indentation of the first item if not newline
1427 self._value[idx].indent = None
1428 comma_in_indent = (
1429 group_rm.indent is not None and "," in group_rm.indent.s
1430 )
1431 comma_in_comma = group_rm.comma is not None and "," in group_rm.comma.s
1432 if comma_in_indent and comma_in_comma:
1433 # Removed group had both commas. Add one to the next group.
1434 group = self._value[idx] if len(self._value) > idx else None
1435 if group is not None:
1436 if group.indent is None:
1437 group.indent = Whitespace(",")
1438 elif "," not in group.indent.s:
1439 # Insert the comma after the newline
1440 try:
1441 newline_index = group.indent.s.index("\n")
1442 group.indent._s = (
1443 group.indent.s[: newline_index + 1]
1444 + ","
1445 + group.indent.s[newline_index + 1 :]
1446 )
1447 except ValueError:
1448 group.indent._s = "," + group.indent.s
1449 elif not comma_in_indent and not comma_in_comma:
1450 # Removed group had no commas. Remove the next comma found.
1451 for j in range(idx, len(self._value)):
1452 group = self._value[j]
1453 if group.indent is not None and "," in group.indent.s:
1454 group.indent._s = group.indent.s.replace(",", "", 1)
1455 break
1456 if group_rm.indent is not None and "\n" in group_rm.indent.s:
1457 # Restore the removed group's newline onto the next group
1458 # if the next group does not have a newline.
1459 # i.e. the two were on the same line
1460 group = self._value[idx] if len(self._value) > idx else None
1461 if group is not None and (
1462 group.indent is None or "\n" not in group.indent.s
1463 ):
1464 group.indent = group_rm.indent
1466 if len(self._value) > 0:
1467 v = self._value[-1]
1468 if not v.is_whitespace():
1469 # remove the comma of the last item
1470 v.comma = None
1472 self._reindex()
1474 def _getstate(self, protocol=3):
1475 return list(self._iter_items()), self._trivia, self._multiline
1478class AbstractTable(Item, _CustomDict):
1479 """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
1481 def __init__(self, value: container.Container, trivia: Trivia):
1482 Item.__init__(self, trivia)
1484 self._value = value
1486 for k, v in self._value.body:
1487 if k is not None:
1488 dict.__setitem__(self, k.key, v)
1490 def unwrap(self) -> dict[str, Any]:
1491 unwrapped = {}
1492 for k, v in self.items():
1493 if isinstance(k, Key):
1494 k = k.key
1495 if hasattr(v, "unwrap"):
1496 v = v.unwrap()
1497 unwrapped[k] = v
1499 return unwrapped
1501 @property
1502 def value(self) -> container.Container:
1503 return self._value
1505 @overload
1506 def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ...
1508 @overload
1509 def append(self: AT, key: Key | str, value: Any) -> AT: ...
1511 def append(self, key, value):
1512 raise NotImplementedError
1514 @overload
1515 def add(self: AT, key: Comment | Whitespace) -> AT: ...
1517 @overload
1518 def add(self: AT, key: Key | str, value: Any = ...) -> AT: ...
1520 def add(self, key, value=None):
1521 if value is None:
1522 if not isinstance(key, (Comment, Whitespace)):
1523 msg = "Non comment/whitespace items must have an associated key"
1524 raise ValueError(msg)
1526 key, value = None, key
1528 return self.append(key, value)
1530 def remove(self: AT, key: Key | str) -> AT:
1531 self._value.remove(key)
1533 if isinstance(key, Key):
1534 key = key.key
1536 if key is not None:
1537 dict.__delitem__(self, key)
1539 return self
1541 def item(self, key: Key | str) -> Item:
1542 return self._value.item(key)
1544 def setdefault(self, key: Key | str, default: Any) -> Any:
1545 super().setdefault(key, default)
1546 return self[key]
1548 def __str__(self):
1549 return str(self.value)
1551 def copy(self: AT) -> AT:
1552 return copy.copy(self)
1554 def __repr__(self) -> str:
1555 return repr(self.value)
1557 def __iter__(self) -> Iterator[str]:
1558 return iter(self._value)
1560 def __len__(self) -> int:
1561 return len(self._value)
1563 def __delitem__(self, key: Key | str) -> None:
1564 self.remove(key)
1566 def __getitem__(self, key: Key | str) -> Item:
1567 return cast(Item, self._value[key])
1569 def __setitem__(self, key: Key | str, value: Any) -> None:
1570 if not isinstance(value, Item):
1571 value = item(value, _parent=self)
1573 is_replace = key in self
1574 self._value[key] = value
1576 if key is not None:
1577 dict.__setitem__(self, key, value)
1579 if is_replace:
1580 return
1581 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1582 if not m:
1583 return
1585 indent = m.group(1)
1587 if not isinstance(value, Whitespace):
1588 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1589 if not m:
1590 value.trivia.indent = indent
1591 else:
1592 value.trivia.indent = m.group(1) + indent + m.group(2)
1595class Table(AbstractTable):
1596 """
1597 A table literal.
1598 """
1600 def __init__(
1601 self,
1602 value: container.Container,
1603 trivia: Trivia,
1604 is_aot_element: bool,
1605 is_super_table: bool | None = None,
1606 name: str | None = None,
1607 display_name: str | None = None,
1608 ) -> None:
1609 super().__init__(value, trivia)
1611 self.name = name
1612 self.display_name = display_name
1613 self._is_aot_element = is_aot_element
1614 self._is_super_table = is_super_table
1616 @property
1617 def discriminant(self) -> int:
1618 return 9
1620 def __copy__(self) -> Table:
1621 return type(self)(
1622 self._value.copy(),
1623 self._trivia.copy(),
1624 self._is_aot_element,
1625 self._is_super_table,
1626 self.name,
1627 self.display_name,
1628 )
1630 def append(self, key: Key | str | None, _item: Any) -> Table:
1631 """
1632 Appends a (key, item) to the table.
1633 """
1634 if not isinstance(_item, Item):
1635 _item = item(_item, _parent=self)
1637 self._value.append(key, _item)
1639 if isinstance(key, Key):
1640 key = next(iter(key)).key
1641 _item = self._value[key]
1643 if key is not None:
1644 dict.__setitem__(self, key, _item)
1646 m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1647 if not m:
1648 return self
1650 indent = m.group(1)
1652 if not isinstance(_item, Whitespace):
1653 m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
1654 if not m:
1655 _item.trivia.indent = indent
1656 else:
1657 _item.trivia.indent = m.group(1) + indent + m.group(2)
1659 return self
1661 def raw_append(self, key: Key | str | None, _item: Any) -> Table:
1662 """Similar to :meth:`append` but does not copy indentation."""
1663 if not isinstance(_item, Item):
1664 _item = item(_item)
1666 self._value.append(key, _item, validate=False)
1668 if isinstance(key, Key):
1669 key = next(iter(key)).key
1670 _item = self._value[key]
1672 if key is not None:
1673 dict.__setitem__(self, key, _item)
1675 return self
1677 def is_aot_element(self) -> bool:
1678 """True if the table is the direct child of an AOT element."""
1679 return self._is_aot_element
1681 def is_super_table(self) -> bool:
1682 """A super table is the intermediate parent of a nested table as in [a.b.c].
1683 If true, it won't appear in the TOML representation."""
1684 if self._is_super_table is not None:
1685 return self._is_super_table
1686 if not self:
1687 return False
1688 # If the table has children and all children are tables, then it is a super table.
1689 for k, child in self.items():
1690 if not isinstance(k, Key):
1691 k = SingleKey(k)
1692 index = self.value._map[k]
1693 if isinstance(index, tuple):
1694 return False
1695 real_key = self.value.body[index][0]
1696 if (
1697 not isinstance(child, (Table, AoT))
1698 or real_key is None
1699 or real_key.is_dotted()
1700 ):
1701 return False
1702 return True
1704 def as_string(self) -> str:
1705 return self._value.as_string()
1707 # Helpers
1709 def indent(self, indent: int) -> Table:
1710 """Indent the table with given number of spaces."""
1711 super().indent(indent)
1713 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1714 if not m:
1715 indent_str = ""
1716 else:
1717 indent_str = m.group(1)
1719 for _, item in self._value.body:
1720 if not isinstance(item, Whitespace):
1721 item.trivia.indent = indent_str + item.trivia.indent
1723 return self
1725 def invalidate_display_name(self):
1726 """Call ``invalidate_display_name`` on the contained tables"""
1727 self.display_name = None
1729 for child in self.values():
1730 if hasattr(child, "invalidate_display_name"):
1731 child.invalidate_display_name()
1733 def _getstate(self, protocol: int = 3) -> tuple:
1734 return (
1735 self._value,
1736 self._trivia,
1737 self._is_aot_element,
1738 self._is_super_table,
1739 self.name,
1740 self.display_name,
1741 )
1744class InlineTable(AbstractTable):
1745 """
1746 An inline table literal.
1747 """
1749 def __init__(
1750 self, value: container.Container, trivia: Trivia, new: bool = False
1751 ) -> None:
1752 super().__init__(value, trivia)
1754 self._new = new
1756 @property
1757 def discriminant(self) -> int:
1758 return 10
1760 def append(self, key: Key | str | None, _item: Any) -> InlineTable:
1761 """
1762 Appends a (key, item) to the table.
1763 """
1764 if not isinstance(_item, Item):
1765 _item = item(_item, _parent=self)
1767 if not isinstance(_item, (Whitespace, Comment)):
1768 if not _item.trivia.indent and len(self._value) > 0 and not self._new:
1769 _item.trivia.indent = " "
1770 if _item.trivia.comment:
1771 _item.trivia.comment = ""
1773 self._value.append(key, _item)
1775 if isinstance(key, Key):
1776 key = key.key
1778 if key is not None:
1779 dict.__setitem__(self, key, _item)
1781 return self
1783 def as_string(self) -> str:
1784 buf = "{"
1785 emitted_key = False
1786 has_explicit_commas = any(
1787 k is None and isinstance(v, Whitespace) and "," in v.s
1788 for k, v in self._value.body
1789 )
1790 last_item_idx = next(
1791 (
1792 i
1793 for i in range(len(self._value.body) - 1, -1, -1)
1794 if self._value.body[i][0] is not None
1795 ),
1796 None,
1797 )
1798 for i, (k, v) in enumerate(self._value.body):
1799 if k is None:
1800 if isinstance(v, Whitespace) and "," in v.s:
1801 if not emitted_key:
1802 buf += v.as_string().replace(",", "", 1)
1803 continue
1805 has_following_null = any(
1806 isinstance(next_v, Null)
1807 for _, next_v in self._value.body[i + 1 :]
1808 )
1809 has_following_key = any(
1810 next_k is not None for next_k, _ in self._value.body[i + 1 :]
1811 )
1812 if has_following_null and not has_following_key:
1813 buf += v.as_string().replace(",", "", 1)
1814 continue
1816 if i == len(self._value.body) - 1:
1817 if self._new:
1818 buf = buf.rstrip(", ")
1819 elif not has_explicit_commas or "," in v.as_string():
1820 buf = buf.rstrip(",")
1822 buf += v.as_string()
1824 continue
1826 v_trivia_trail = v.trivia.trail.replace("\n", "")
1827 buf += (
1828 f"{v.trivia.indent}"
1829 f"{k.as_string() + ('.' if k.is_dotted() else '')}"
1830 f"{k.sep}"
1831 f"{v.as_string()}"
1832 f"{v.trivia.comment}"
1833 f"{v_trivia_trail}"
1834 )
1835 emitted_key = True
1837 if (
1838 not has_explicit_commas
1839 and last_item_idx is not None
1840 and i < last_item_idx
1841 ):
1842 buf += ","
1843 if self._new:
1844 buf += " "
1846 buf += "}"
1848 return buf
1850 def __setitem__(self, key: Key | str, value: Any) -> None:
1851 if hasattr(value, "trivia") and value.trivia.comment:
1852 value.trivia.comment = ""
1853 super().__setitem__(key, value)
1855 def __copy__(self) -> InlineTable:
1856 return type(self)(self._value.copy(), self._trivia.copy(), self._new)
1858 def _getstate(self, protocol: int = 3) -> tuple:
1859 return (self._value, self._trivia)
1862class String(str, Item):
1863 """
1864 A string literal.
1865 """
1867 def __new__(cls, t, value, original, trivia):
1868 return super().__new__(cls, value)
1870 def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
1871 super().__init__(trivia)
1873 self._t = t
1874 self._original = original
1876 def unwrap(self) -> str:
1877 return str(self)
1879 @property
1880 def discriminant(self) -> int:
1881 return 11
1883 @property
1884 def value(self) -> str:
1885 return self
1887 def as_string(self) -> str:
1888 return f"{self._t.value}{decode(self._original)}{self._t.value}"
1890 @property
1891 def type(self) -> StringType:
1892 return self._t
1894 def __add__(self: ItemT, other: str) -> ItemT:
1895 if not isinstance(other, str):
1896 return NotImplemented
1897 result = super().__add__(other)
1898 original = self._original + getattr(other, "_original", other)
1900 return self._new(result, original)
1902 def _new(self, result: str, original: str) -> String:
1903 return String(self._t, result, original, self._trivia)
1905 def _getstate(self, protocol=3):
1906 return self._t, str(self), self._original, self._trivia
1908 @classmethod
1909 def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String:
1910 value = decode(value)
1912 invalid = type_.invalid_sequences
1913 if any(c in value for c in invalid):
1914 raise InvalidStringError(value, invalid, type_.value)
1916 escaped = type_.escaped_sequences
1917 string_value = escape_string(value, escaped) if escape and escaped else value
1919 return cls(type_, decode(value), string_value, Trivia())
1922class AoT(Item, _CustomList):
1923 """
1924 An array of table literal
1925 """
1927 def __init__(
1928 self, body: list[Table], name: str | None = None, parsed: bool = False
1929 ) -> None:
1930 self.name = name
1931 self._body: list[Table] = []
1932 self._parsed = parsed
1934 super().__init__(Trivia(trail=""))
1936 for table in body:
1937 self.append(table)
1939 def unwrap(self) -> list[dict[str, Any]]:
1940 unwrapped = []
1941 for t in self._body:
1942 if hasattr(t, "unwrap"):
1943 unwrapped.append(t.unwrap())
1944 else:
1945 unwrapped.append(t)
1946 return unwrapped
1948 @property
1949 def body(self) -> list[Table]:
1950 return self._body
1952 @property
1953 def discriminant(self) -> int:
1954 return 12
1956 @property
1957 def value(self) -> list[dict[Any, Any]]:
1958 return [v.value for v in self._body]
1960 def __len__(self) -> int:
1961 return len(self._body)
1963 @overload
1964 def __getitem__(self, key: slice) -> list[Table]: ...
1966 @overload
1967 def __getitem__(self, key: int) -> Table: ...
1969 def __getitem__(self, key):
1970 return self._body[key]
1972 def __setitem__(self, key: slice | int, value: Any) -> None:
1973 self._body[key] = item(value, _parent=self)
1975 def __delitem__(self, key: slice | int) -> None:
1976 del self._body[key]
1977 list.__delitem__(self, key)
1979 def insert(self, index: int, value: dict) -> None:
1980 value = item(value, _parent=self)
1981 if not isinstance(value, Table):
1982 raise ValueError(f"Unsupported insert value type: {type(value)}")
1983 length = len(self)
1984 if index < 0:
1985 index += length
1986 if index < 0:
1987 index = 0
1988 elif index >= length:
1989 index = length
1990 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1991 if m:
1992 indent = m.group(1)
1994 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1995 if not m:
1996 value.trivia.indent = indent
1997 else:
1998 value.trivia.indent = m.group(1) + indent + m.group(2)
1999 prev_table = self._body[index - 1] if 0 < index and length else None
2000 next_table = self._body[index + 1] if index < length - 1 else None
2001 if not self._parsed:
2002 if prev_table and "\n" not in value.trivia.indent:
2003 value.trivia.indent = "\n" + value.trivia.indent
2004 if next_table and "\n" not in next_table.trivia.indent:
2005 next_table.trivia.indent = "\n" + next_table.trivia.indent
2006 self._body.insert(index, value)
2007 list.insert(self, index, value)
2009 def invalidate_display_name(self):
2010 """Call ``invalidate_display_name`` on the contained tables"""
2011 for child in self:
2012 if hasattr(child, "invalidate_display_name"):
2013 child.invalidate_display_name()
2015 def as_string(self) -> str:
2016 b = ""
2017 for table in self._body:
2018 b += table.as_string()
2020 return b
2022 def __repr__(self) -> str:
2023 return f"<AoT {self.value}>"
2025 def _getstate(self, protocol=3):
2026 return self._body, self.name, self._parsed
2029class Null(Item):
2030 """
2031 A null item.
2032 """
2034 def __init__(self) -> None:
2035 super().__init__(Trivia(trail=""))
2037 def unwrap(self) -> None:
2038 return None
2040 @property
2041 def discriminant(self) -> int:
2042 return -1
2044 @property
2045 def value(self) -> None:
2046 return None
2048 def as_string(self) -> str:
2049 return ""
2051 def _getstate(self, protocol=3) -> tuple:
2052 return ()