Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/items.py: 60%
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 timedelta
18from datetime import tzinfo
19from enum import Enum
20from typing import TYPE_CHECKING
21from typing import Any
22from typing import TypeVar
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._utils import CONTROL_CHARS
32from tomlkit._utils import escape_string
33from tomlkit.exceptions import ConvertError
34from tomlkit.exceptions import InvalidStringError
37if TYPE_CHECKING:
38 from typing import Protocol
40 from tomlkit import container
41 from tomlkit.container import OutOfOrderTableProxy
43 class Encoder(Protocol):
44 def __call__(self, __value: Any, /) -> Item: ...
47ItemT = TypeVar("ItemT", bound="Item")
48CUSTOM_ENCODERS: list[Encoder] = []
49AT = TypeVar("AT", bound="AbstractTable")
52@overload
53def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ... # type: ignore[overload-overlap]
56@overload
57def item(value: int, _parent: Item | None = ..., _sort_keys: bool = ...) -> Integer: ...
60@overload
61def item(value: float, _parent: Item | None = ..., _sort_keys: bool = ...) -> Float: ...
64@overload
65def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> String: ...
68@overload
69def item( # type: ignore[overload-overlap]
70 value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...
71) -> DateTime: ...
74@overload
75def item(value: date, _parent: Item | None = ..., _sort_keys: bool = ...) -> Date: ...
78@overload
79def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Time: ...
82@overload
83def item(
84 value: Sequence[dict[str, Any]], _parent: Item | None = ..., _sort_keys: bool = ...
85) -> AoT: ...
88@overload
89def item(
90 value: Sequence[Any], _parent: Item | None = ..., _sort_keys: bool = ...
91) -> Array: ...
94@overload
95def item(
96 value: dict[str, Any], _parent: Array = ..., _sort_keys: bool = ...
97) -> InlineTable: ...
100@overload
101def item(
102 value: dict[str, Any], _parent: Item | None = ..., _sort_keys: bool = ...
103) -> Table: ...
106@overload
107def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ...
110@overload
111def item(value: object, _parent: Item | None = ..., _sort_keys: bool = ...) -> Item: ...
114def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item:
115 """Create a TOML item from a Python object.
117 :Example:
119 >>> item(42)
120 42
121 >>> item([1, 2, 3])
122 [1, 2, 3]
123 >>> item({'a': 1, 'b': 2})
124 a = 1
125 b = 2
126 """
128 from tomlkit.container import Container
130 if isinstance(value, Item):
131 return value
133 if isinstance(value, bool):
134 return Bool(value, Trivia())
135 elif isinstance(value, int):
136 return Integer(value, Trivia(), str(value))
137 elif isinstance(value, float):
138 return Float(value, Trivia(), str(value))
139 elif isinstance(value, dict):
140 table_constructor = (
141 InlineTable if isinstance(_parent, (Array, InlineTable)) else Table
142 )
143 val = table_constructor(Container(), Trivia(), False)
144 for k, v in sorted(
145 value.items(),
146 key=lambda i: (isinstance(i[1], dict), i[0]) if _sort_keys else 1,
147 ):
148 val[k] = item(v, _parent=val, _sort_keys=_sort_keys)
150 return val
151 elif isinstance(value, (list, tuple)):
152 a: AoT | Array
153 if (
154 value
155 and all(isinstance(v, dict) for v in value)
156 and (_parent is None or isinstance(_parent, Table))
157 ):
158 a = AoT([])
159 table_constructor = Table
160 else:
161 a = Array([], Trivia())
162 table_constructor = InlineTable
164 for v in value:
165 if isinstance(v, dict):
166 table = table_constructor(Container(), Trivia(), True)
168 for k, _v in sorted(
169 v.items(),
170 key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
171 ):
172 i = item(_v, _parent=table, _sort_keys=_sort_keys)
173 if isinstance(table, InlineTable):
174 i.trivia.trail = ""
176 table[k] = i
178 v = table
180 a.append(v)
182 return a
183 elif isinstance(value, str):
184 return String.from_raw(value)
185 elif isinstance(value, datetime):
186 return DateTime(
187 value.year,
188 value.month,
189 value.day,
190 value.hour,
191 value.minute,
192 value.second,
193 value.microsecond,
194 value.tzinfo,
195 Trivia(),
196 value.isoformat().replace("+00:00", "Z"),
197 )
198 elif isinstance(value, date):
199 return Date(value.year, value.month, value.day, Trivia(), value.isoformat())
200 elif isinstance(value, time):
201 return Time(
202 value.hour,
203 value.minute,
204 value.second,
205 value.microsecond,
206 value.tzinfo,
207 Trivia(),
208 value.isoformat(),
209 )
210 else:
211 for encoder in CUSTOM_ENCODERS:
212 try:
213 # Check if encoder accepts keyword arguments for backward compatibility
214 sig = inspect.signature(encoder)
215 if "_parent" in sig.parameters or any(
216 p.kind == p.VAR_KEYWORD for p in sig.parameters.values()
217 ):
218 # New style encoder that can accept additional parameters
219 rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys) # type: ignore[call-arg]
220 else:
221 # Old style encoder that only accepts value
222 rv = encoder(value)
223 except ConvertError:
224 pass
225 else:
226 if not isinstance(rv, Item):
227 raise ConvertError(
228 f"Custom encoder is expected to return an instance of Item, got {type(rv)}"
229 )
230 return rv
232 raise ConvertError(f"Unable to convert an object of {type(value)} to a TOML item")
235class StringType(Enum):
236 # Single Line Basic
237 SLB = '"'
238 # Multi Line Basic
239 MLB = '"""'
240 # Single Line Literal
241 SLL = "'"
242 # Multi Line Literal
243 MLL = "'''"
245 @classmethod
246 def select(cls, literal: bool = False, multiline: bool = False) -> StringType:
247 return {
248 (False, False): cls.SLB,
249 (False, True): cls.MLB,
250 (True, False): cls.SLL,
251 (True, True): cls.MLL,
252 }[(literal, multiline)]
254 @property
255 def escaped_sequences(self) -> Collection[str]:
256 # https://toml.io/en/v1.0.0#string
257 escaped_in_basic = CONTROL_CHARS | {"\\"}
258 allowed_in_multiline = {"\n", "\r"}
259 return {
260 StringType.SLB: escaped_in_basic | {'"'},
261 StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline,
262 StringType.SLL: (),
263 StringType.MLL: (),
264 }[self]
266 @property
267 def invalid_sequences(self) -> Collection[str]:
268 # https://toml.io/en/v1.0.0#string
269 forbidden_in_literal = CONTROL_CHARS - {"\t"}
270 allowed_in_multiline = {"\n", "\r"}
271 return {
272 StringType.SLB: (),
273 StringType.MLB: (),
274 StringType.SLL: forbidden_in_literal | {"'"},
275 StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline,
276 }[self]
278 @property
279 def unit(self) -> str:
280 return self.value[0]
282 def is_basic(self) -> bool:
283 return self is StringType.SLB or self is StringType.MLB
285 def is_literal(self) -> bool:
286 return self is StringType.SLL or self is StringType.MLL
288 def is_singleline(self) -> bool:
289 return self is StringType.SLB or self is StringType.SLL
291 def is_multiline(self) -> bool:
292 return self is StringType.MLB or self is StringType.MLL
294 def toggle(self) -> StringType:
295 return {
296 StringType.SLB: StringType.MLB,
297 StringType.MLB: StringType.SLB,
298 StringType.SLL: StringType.MLL,
299 StringType.MLL: StringType.SLL,
300 }[self]
303class BoolType(Enum):
304 TRUE = "true"
305 FALSE = "false"
307 def __bool__(self) -> bool:
308 return {BoolType.TRUE: True, BoolType.FALSE: False}[self]
310 def __iter__(self) -> Iterator[str]:
311 return iter(self.value)
313 def __len__(self) -> int:
314 return len(self.value)
317@dataclasses.dataclass
318class Trivia:
319 """
320 Trivia information (aka metadata).
321 """
323 # Whitespace before a value.
324 indent: str = ""
325 # Whitespace after a value, but before a comment.
326 comment_ws: str = ""
327 # Comment, starting with # character, or empty string if no comment.
328 comment: str = ""
329 # Trailing newline.
330 trail: str = "\n"
332 def copy(self) -> Trivia:
333 return dataclasses.replace(self)
336class KeyType(Enum):
337 """
338 The type of a Key.
340 Keys can be bare (unquoted), or quoted using basic ("), or literal (')
341 quotes following the same escaping rules as single-line StringType.
342 """
344 Bare = ""
345 Basic = '"'
346 Literal = "'"
349class Key(abc.ABC):
350 """Base class for a key"""
352 sep: str
353 _original: str
354 _keys: list[SingleKey]
355 _dotted: bool
356 key: str
358 @abc.abstractmethod
359 def __hash__(self) -> int:
360 pass
362 @abc.abstractmethod
363 def __eq__(self, __o: object) -> bool:
364 pass
366 def is_dotted(self) -> bool:
367 """If the key is followed by other keys"""
368 return self._dotted
370 def __iter__(self) -> Iterator[SingleKey]:
371 return iter(self._keys)
373 def concat(self, other: Key) -> DottedKey:
374 """Concatenate keys into a dotted key"""
375 keys = self._keys + other._keys
376 return DottedKey(keys, sep=self.sep)
378 def is_multi(self) -> bool:
379 """Check if the key contains multiple keys"""
380 return len(self._keys) > 1
382 def as_string(self) -> str:
383 """The TOML representation"""
384 return self._original
386 def __str__(self) -> str:
387 return self.as_string()
389 def __repr__(self) -> str:
390 return f"<Key {self.as_string()}>"
393class SingleKey(Key):
394 """A single key"""
396 def __init__(
397 self,
398 k: str,
399 t: KeyType | None = None,
400 sep: str | None = None,
401 original: str | None = None,
402 ) -> None:
403 if not isinstance(k, str):
404 raise TypeError("Keys must be strings")
406 if t is None:
407 if not k or any(
408 c not in string.ascii_letters + string.digits + "-" + "_" for c in k
409 ):
410 t = KeyType.Basic
411 else:
412 t = KeyType.Bare
414 self.t = t
415 if sep is None:
416 sep = " = "
418 self.sep = sep
419 self.key = k
420 if original is None:
421 key_str = escape_string(k) if t == KeyType.Basic else k
422 original = f"{t.value}{key_str}{t.value}"
424 self._original = original
425 self._keys = [self]
426 self._dotted = False
428 @property
429 def delimiter(self) -> str:
430 """The delimiter: double quote/single quote/none"""
431 return self.t.value
433 def is_bare(self) -> bool:
434 """Check if the key is bare"""
435 return self.t == KeyType.Bare
437 def __hash__(self) -> int:
438 return hash(self.key)
440 def __eq__(self, other: Any) -> bool:
441 if isinstance(other, Key):
442 return isinstance(other, SingleKey) and self.key == other.key
444 return bool(self.key == other)
447class DottedKey(Key):
448 def __init__(
449 self,
450 keys: Iterable[SingleKey],
451 sep: str | None = None,
452 original: str | None = None,
453 ) -> None:
454 self._keys = list(keys)
455 if original is None:
456 original = ".".join(k.as_string() for k in self._keys)
458 self.sep = " = " if sep is None else sep
459 self._original = original
460 self._dotted = False
461 self.key = ".".join(k.key for k in self._keys)
463 def __hash__(self) -> int:
464 return hash(tuple(self._keys))
466 def __eq__(self, __o: object) -> bool:
467 return isinstance(__o, DottedKey) and self._keys == __o._keys
470class Item:
471 """
472 An item within a TOML document.
473 """
475 def __init__(self, trivia: Trivia) -> None:
476 self._trivia = trivia
478 @property
479 def trivia(self) -> Trivia:
480 """The trivia element associated with this item"""
481 return self._trivia
483 @property
484 def discriminant(self) -> int:
485 raise NotImplementedError()
487 def as_string(self) -> str:
488 """The TOML representation"""
489 raise NotImplementedError()
491 @property
492 def value(self) -> Any:
493 return self
495 def unwrap(self) -> Any:
496 """Returns as pure python object (ppo)"""
497 raise NotImplementedError()
499 # Helpers
501 def comment(self, comment: str) -> Item:
502 """Attach a comment to this item"""
503 if "\n" in comment or "\r" in comment:
504 raise ValueError("Comment cannot contain line breaks")
505 if not comment.strip().startswith("#"):
506 comment = "# " + comment
508 self._trivia.comment_ws = " "
509 self._trivia.comment = comment
511 return self
513 def indent(self, indent: int) -> Item:
514 """Indent this item with given number of spaces"""
515 if self._trivia.indent.startswith("\n"):
516 self._trivia.indent = "\n" + " " * indent
517 else:
518 self._trivia.indent = " " * indent
520 return self
522 def is_boolean(self) -> bool:
523 return isinstance(self, Bool)
525 def is_table(self) -> bool:
526 return isinstance(self, Table)
528 def is_inline_table(self) -> bool:
529 return isinstance(self, InlineTable)
531 def is_aot(self) -> bool:
532 return isinstance(self, AoT)
534 def _getstate(self, protocol: int = 3) -> tuple[object, ...]:
535 return (self._trivia,)
537 def __reduce__(self) -> tuple[type, tuple[object, ...]]:
538 return self.__reduce_ex__(2)
540 def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[object, ...]]: # type: ignore[override]
541 return self.__class__, self._getstate(protocol)
544class Whitespace(Item):
545 """
546 A whitespace literal.
547 """
549 def __init__(self, s: str, fixed: bool = False) -> None:
550 self._s = s
551 self._fixed = fixed
553 @property
554 def s(self) -> str:
555 return self._s
557 @property
558 def value(self) -> str:
559 """The wrapped string of the whitespace"""
560 return self._s
562 @property
563 def trivia(self) -> Trivia:
564 raise RuntimeError("Called trivia on a Whitespace variant.")
566 @property
567 def discriminant(self) -> int:
568 return 0
570 def is_fixed(self) -> bool:
571 """If the whitespace is fixed, it can't be merged or discarded from the output."""
572 return self._fixed
574 def as_string(self) -> str:
575 return self._s
577 def __repr__(self) -> str:
578 return f"<{self.__class__.__name__} {self._s!r}>"
580 def _getstate(self, protocol: int = 3) -> tuple[str, bool]:
581 return self._s, self._fixed
584class Comment(Item):
585 """
586 A comment literal.
587 """
589 @property
590 def discriminant(self) -> int:
591 return 1
593 def as_string(self) -> str:
594 return (
595 f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
596 )
598 def __str__(self) -> str:
599 return f"{self._trivia.indent}{decode(self._trivia.comment)}"
602class Integer(Item, _CustomInt):
603 """
604 An integer literal.
605 """
607 def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
608 return int.__new__(cls, value)
610 def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
611 super().__init__(trivia)
612 self._original = value
613 self._raw = raw
614 self._sign = False
616 if re.match(r"^[+\-]\d+$", raw):
617 self._sign = True
619 def unwrap(self) -> int:
620 return self._original
622 __int__ = unwrap
624 def __hash__(self) -> int:
625 return hash(self.unwrap())
627 @property
628 def discriminant(self) -> int:
629 return 2
631 @property
632 def value(self) -> int:
633 """The wrapped integer value"""
634 return self
636 def as_string(self) -> str:
637 return self._raw
639 def _new(self, result: int) -> Integer:
640 raw = str(result)
641 if self._sign and result >= 0:
642 raw = f"+{raw}"
644 return Integer(result, self._trivia, raw)
646 def _getstate(self, protocol: int = 3) -> tuple[int, Trivia, str]:
647 return int(self), self._trivia, self._raw
649 # int methods — explicit typed wrappers
650 def __abs__(self) -> Integer:
651 return self._new(int.__abs__(self))
653 def __add__(self, other: object) -> Integer:
654 result = int.__add__(self, other) # type: ignore[operator]
655 if result is NotImplemented:
656 return result # type: ignore[return-value]
657 return self._new(result)
659 def __and__(self, other: object) -> Integer:
660 result = int.__and__(self, other) # type: ignore[operator]
661 if result is NotImplemented:
662 return result # type: ignore[return-value]
663 return self._new(result)
665 def __ceil__(self) -> Integer:
666 return self._new(int.__ceil__(self))
668 __eq__ = int.__eq__
670 def __floor__(self) -> Integer:
671 return self._new(int.__floor__(self))
673 def __floordiv__(self, other: object) -> Integer:
674 result = int.__floordiv__(self, other) # type: ignore[operator]
675 if result is NotImplemented:
676 return result # type: ignore[return-value]
677 return self._new(result)
679 def __invert__(self) -> Integer:
680 return self._new(int.__invert__(self))
682 __le__ = int.__le__
684 def __lshift__(self, other: object) -> Integer:
685 result = int.__lshift__(self, other) # type: ignore[operator]
686 if result is NotImplemented:
687 return result # type: ignore[return-value]
688 return self._new(result)
690 __lt__ = int.__lt__
692 def __mod__(self, other: object) -> Integer:
693 result = int.__mod__(self, other) # type: ignore[operator]
694 if result is NotImplemented:
695 return result # type: ignore[return-value]
696 return self._new(result)
698 def __mul__(self, other: object) -> Integer:
699 result = int.__mul__(self, other) # type: ignore[operator]
700 if result is NotImplemented:
701 return result # type: ignore[return-value]
702 return self._new(result)
704 def __neg__(self) -> Integer:
705 return self._new(int.__neg__(self))
707 def __or__(self, other: object) -> Integer:
708 result = int.__or__(self, other) # type: ignore[operator]
709 if result is NotImplemented:
710 return result # type: ignore[return-value]
711 return self._new(result)
713 def __pos__(self) -> Integer:
714 return self._new(int.__pos__(self))
716 def __pow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[override]
717 result = (
718 int.__pow__(self, other) if mod is None else int.__pow__(self, other, mod)
719 )
720 return self._new(result)
722 def __radd__(self, other: object) -> Integer:
723 result = int.__radd__(self, other) # type: ignore[operator]
724 if result is NotImplemented:
725 return result # type: ignore[return-value]
726 return self._new(result)
728 def __rand__(self, other: object) -> Integer:
729 result = int.__rand__(self, other) # type: ignore[operator]
730 if result is NotImplemented:
731 return result # type: ignore[return-value]
732 return self._new(result)
734 def __rfloordiv__(self, other: object) -> Integer:
735 result = int.__rfloordiv__(self, other) # type: ignore[operator]
736 if result is NotImplemented:
737 return result # type: ignore[return-value]
738 return self._new(result)
740 def __rlshift__(self, other: object) -> Integer:
741 result = int.__rlshift__(self, other) # type: ignore[operator]
742 if result is NotImplemented:
743 return result # type: ignore[return-value]
744 return self._new(result)
746 def __rmod__(self, other: object) -> Integer:
747 result = int.__rmod__(self, other) # type: ignore[operator]
748 if result is NotImplemented:
749 return result # type: ignore[return-value]
750 return self._new(result)
752 def __rmul__(self, other: object) -> Integer:
753 result = int.__rmul__(self, other) # type: ignore[operator]
754 if result is NotImplemented:
755 return result # type: ignore[return-value]
756 return self._new(result)
758 def __ror__(self, other: object) -> Integer:
759 result = int.__ror__(self, other) # type: ignore[operator]
760 if result is NotImplemented:
761 return result # type: ignore[return-value]
762 return self._new(result)
764 def __round__(self, ndigits: int = 0) -> Integer: # type: ignore[override]
765 return self._new(int.__round__(self, ndigits))
767 def __rpow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[misc]
768 result = (
769 int.__rpow__(self, other) if mod is None else int.__rpow__(self, other, mod)
770 )
771 return self._new(result)
773 def __rrshift__(self, other: object) -> Integer:
774 result = int.__rrshift__(self, other) # type: ignore[operator]
775 if result is NotImplemented:
776 return result # type: ignore[return-value]
777 return self._new(result)
779 def __rshift__(self, other: object) -> Integer:
780 result = int.__rshift__(self, other) # type: ignore[operator]
781 if result is NotImplemented:
782 return result # type: ignore[return-value]
783 return self._new(result)
785 def __rxor__(self, other: object) -> Integer:
786 result = int.__rxor__(self, other) # type: ignore[operator]
787 if result is NotImplemented:
788 return result # type: ignore[return-value]
789 return self._new(result)
791 def __sub__(self, other: object) -> Integer:
792 result = int.__sub__(self, other) # type: ignore[operator]
793 if result is NotImplemented:
794 return result # type: ignore[return-value]
795 return self._new(result)
797 def __rsub__(self, other: object) -> Integer:
798 result = int.__rsub__(self, other) # type: ignore[operator]
799 if result is NotImplemented:
800 return result # type: ignore[return-value]
801 return self._new(result)
803 def __trunc__(self) -> Integer:
804 return self._new(int.__trunc__(self))
806 def __xor__(self, other: object) -> Integer:
807 result = int.__xor__(self, other) # type: ignore[operator]
808 if result is NotImplemented:
809 return result # type: ignore[return-value]
810 return self._new(result)
812 def __rtruediv__(self, other: object) -> Float:
813 result = int.__rtruediv__(self, other) # type: ignore[operator]
814 if result is NotImplemented:
815 return result # type: ignore[return-value]
816 return Float._new(self, result) # type: ignore[arg-type]
818 def __truediv__(self, other: object) -> Float:
819 result = int.__truediv__(self, other) # type: ignore[operator]
820 if result is NotImplemented:
821 return result # type: ignore[return-value]
822 return Float._new(self, result) # type: ignore[arg-type]
825class Float(Item, _CustomFloat):
826 """
827 A float literal.
828 """
830 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
831 return float.__new__(cls, value)
833 def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
834 super().__init__(trivia)
835 self._original = value
836 self._raw = raw
837 self._sign = False
839 if re.match(r"^[+\-].+$", raw):
840 self._sign = True
842 def unwrap(self) -> float:
843 return self._original
845 __float__ = unwrap
847 def __hash__(self) -> int:
848 return hash(self.unwrap())
850 @property
851 def discriminant(self) -> int:
852 return 3
854 @property
855 def value(self) -> float:
856 """The wrapped float value"""
857 return self
859 def as_string(self) -> str:
860 return self._raw
862 def _new(self, result: float) -> Float:
863 raw = str(result)
865 if self._sign and result >= 0:
866 raw = f"+{raw}"
868 return Float(result, self._trivia, raw)
870 def _getstate(self, protocol: int = 3) -> tuple[float, Trivia, str]:
871 return float(self), self._trivia, self._raw
873 # float methods — explicit typed wrappers
874 def __abs__(self) -> Float:
875 return self._new(float.__abs__(self))
877 def __add__(self, other: object) -> Float:
878 result = float.__add__(self, other) # type: ignore[operator]
879 if result is NotImplemented:
880 return result # type: ignore[return-value]
881 return self._new(result)
883 __eq__ = float.__eq__
885 def __floordiv__(self, other: object) -> Float:
886 result = float.__floordiv__(self, other) # type: ignore[operator]
887 if result is NotImplemented:
888 return result # type: ignore[return-value]
889 return self._new(result)
891 __le__ = float.__le__
892 __lt__ = float.__lt__
894 def __mod__(self, other: object) -> Float:
895 result = float.__mod__(self, other) # type: ignore[operator]
896 if result is NotImplemented:
897 return result # type: ignore[return-value]
898 return self._new(result)
900 def __mul__(self, other: object) -> Float:
901 result = float.__mul__(self, other) # type: ignore[operator]
902 if result is NotImplemented:
903 return result # type: ignore[return-value]
904 return self._new(result)
906 def __neg__(self) -> Float:
907 return self._new(float.__neg__(self))
909 def __pos__(self) -> Float:
910 return self._new(float.__pos__(self))
912 def __pow__(self, other: object, mod: None = None) -> Float:
913 result = float.__pow__(self, other) # type: ignore[operator]
914 if result is NotImplemented:
915 return result # type: ignore[no-any-return]
916 return self._new(result)
918 def __radd__(self, other: object) -> Float:
919 result = float.__radd__(self, other) # type: ignore[operator]
920 if result is NotImplemented:
921 return result # type: ignore[return-value]
922 return self._new(result)
924 def __rfloordiv__(self, other: object) -> Float:
925 result = float.__rfloordiv__(self, other) # type: ignore[operator]
926 if result is NotImplemented:
927 return result # type: ignore[return-value]
928 return self._new(result)
930 def __rmod__(self, other: object) -> Float:
931 result = float.__rmod__(self, other) # type: ignore[operator]
932 if result is NotImplemented:
933 return result # type: ignore[return-value]
934 return self._new(result)
936 def __rmul__(self, other: object) -> Float:
937 result = float.__rmul__(self, other) # type: ignore[operator]
938 if result is NotImplemented:
939 return result # type: ignore[return-value]
940 return self._new(result)
942 def __round__(self, ndigits: int = 0) -> Float: # type: ignore[override]
943 return self._new(float.__round__(self, ndigits))
945 def __rpow__(self, other: object, mod: None = None) -> Float:
946 result = float.__rpow__(self, other) # type: ignore[operator]
947 if result is NotImplemented:
948 return result # type: ignore[no-any-return]
949 return self._new(result)
951 def __rtruediv__(self, other: object) -> Float:
952 result = float.__rtruediv__(self, other) # type: ignore[operator]
953 if result is NotImplemented:
954 return result # type: ignore[return-value]
955 return self._new(result)
957 def __truediv__(self, other: object) -> Float:
958 result = float.__truediv__(self, other) # type: ignore[operator]
959 if result is NotImplemented:
960 return result # type: ignore[return-value]
961 return self._new(result)
963 def __sub__(self, other: object) -> Float:
964 result = float.__sub__(self, other) # type: ignore[operator]
965 if result is NotImplemented:
966 return result # type: ignore[return-value]
967 return self._new(result)
969 def __rsub__(self, other: object) -> Float:
970 result = float.__rsub__(self, other) # type: ignore[operator]
971 if result is NotImplemented:
972 return result # type: ignore[return-value]
973 return self._new(result)
975 __trunc__ = float.__trunc__
976 __ceil__ = float.__ceil__
977 __floor__ = float.__floor__
980class Bool(Item):
981 """
982 A boolean literal.
983 """
985 def __init__(self, t: int | BoolType, trivia: Trivia) -> None:
986 super().__init__(trivia)
988 self._value = bool(t)
990 def unwrap(self) -> bool:
991 return bool(self)
993 @property
994 def discriminant(self) -> int:
995 return 4
997 @property
998 def value(self) -> bool:
999 """The wrapped boolean value"""
1000 return self._value
1002 def as_string(self) -> str:
1003 return str(self._value).lower()
1005 def _getstate(self, protocol: int = 3) -> tuple[bool, Trivia]:
1006 return self._value, self._trivia
1008 def __bool__(self) -> bool:
1009 return self._value
1011 __nonzero__ = __bool__
1013 def __eq__(self, other: object) -> bool:
1014 if not isinstance(other, bool):
1015 return NotImplemented
1017 return other == self._value
1019 def __hash__(self) -> int:
1020 return hash(self._value)
1022 def __repr__(self) -> str:
1023 return repr(self._value)
1026class DateTime(Item, datetime):
1027 """
1028 A datetime literal.
1029 """
1031 def __new__(
1032 cls,
1033 year: int,
1034 month: int,
1035 day: int,
1036 hour: int,
1037 minute: int,
1038 second: int,
1039 microsecond: int,
1040 tzinfo: tzinfo | None,
1041 trivia: Trivia | None = None,
1042 raw: str | None = None,
1043 **kwargs: object,
1044 ) -> DateTime:
1045 return datetime.__new__(
1046 cls,
1047 year,
1048 month,
1049 day,
1050 hour,
1051 minute,
1052 second,
1053 microsecond,
1054 tzinfo=tzinfo,
1055 )
1057 def __init__(
1058 self,
1059 year: int,
1060 month: int,
1061 day: int,
1062 hour: int,
1063 minute: int,
1064 second: int,
1065 microsecond: int,
1066 tzinfo: tzinfo | None,
1067 trivia: Trivia | None = None,
1068 raw: str | None = None,
1069 **kwargs: object,
1070 ) -> None:
1071 super().__init__(trivia or Trivia())
1073 self._raw = raw or self.isoformat()
1075 def unwrap(self) -> datetime:
1076 (
1077 year,
1078 month,
1079 day,
1080 hour,
1081 minute,
1082 second,
1083 microsecond,
1084 tzinfo,
1085 _,
1086 _,
1087 ) = self._getstate()
1088 return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
1090 @property
1091 def discriminant(self) -> int:
1092 return 5
1094 @property
1095 def value(self) -> datetime:
1096 return self
1098 def as_string(self) -> str:
1099 return self._raw
1101 def __add__(self, other: timedelta) -> DateTime:
1102 if PY38:
1103 result = datetime(
1104 self.year,
1105 self.month,
1106 self.day,
1107 self.hour,
1108 self.minute,
1109 self.second,
1110 self.microsecond,
1111 self.tzinfo,
1112 ).__add__(other)
1113 else:
1114 result = super().__add__(other)
1116 return self._new(result)
1118 @overload # type: ignore[override]
1119 def __sub__(self, other: timedelta) -> DateTime: ...
1121 @overload
1122 def __sub__(self, other: datetime) -> timedelta: ...
1124 def __sub__(self, other: timedelta | datetime) -> DateTime | timedelta:
1125 if PY38:
1126 result = datetime(
1127 self.year,
1128 self.month,
1129 self.day,
1130 self.hour,
1131 self.minute,
1132 self.second,
1133 self.microsecond,
1134 self.tzinfo,
1135 ).__sub__(other)
1136 else:
1137 result = super().__sub__(other) # type: ignore[operator]
1139 if isinstance(result, datetime):
1140 result = self._new(result)
1142 return result
1144 def replace(self, *args: object, **kwargs: object) -> DateTime:
1145 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type]
1147 def astimezone(self, tz: tzinfo) -> DateTime: # type: ignore[override]
1148 result = super().astimezone(tz)
1149 if PY38:
1150 return result
1151 return self._new(result)
1153 def _new(self, result: datetime) -> DateTime:
1154 raw = result.isoformat()
1156 return DateTime(
1157 result.year,
1158 result.month,
1159 result.day,
1160 result.hour,
1161 result.minute,
1162 result.second,
1163 result.microsecond,
1164 result.tzinfo,
1165 self._trivia,
1166 raw,
1167 )
1169 def _getstate(
1170 self, protocol: int = 3
1171 ) -> tuple[int, int, int, int, int, int, int, tzinfo | None, Trivia, str]:
1172 return (
1173 self.year,
1174 self.month,
1175 self.day,
1176 self.hour,
1177 self.minute,
1178 self.second,
1179 self.microsecond,
1180 self.tzinfo,
1181 self._trivia,
1182 self._raw,
1183 )
1186class Date(Item, date):
1187 """
1188 A date literal.
1189 """
1191 def __new__(
1192 cls,
1193 year: int,
1194 month: int,
1195 day: int,
1196 trivia: Trivia | None = None,
1197 raw: str = "",
1198 ) -> Date:
1199 return date.__new__(cls, year, month, day)
1201 def __init__(
1202 self,
1203 year: int,
1204 month: int,
1205 day: int,
1206 trivia: Trivia | None = None,
1207 raw: str = "",
1208 ) -> None:
1209 super().__init__(trivia or Trivia())
1211 self._raw = raw
1213 def unwrap(self) -> date:
1214 (year, month, day, _, _) = self._getstate()
1215 return date(year, month, day)
1217 @property
1218 def discriminant(self) -> int:
1219 return 6
1221 @property
1222 def value(self) -> date:
1223 return self
1225 def as_string(self) -> str:
1226 return self._raw
1228 def __add__(self, other: timedelta) -> Date:
1229 if PY38:
1230 result = date(self.year, self.month, self.day).__add__(other)
1231 else:
1232 result = super().__add__(other)
1234 return self._new(result)
1236 @overload # type: ignore[override]
1237 def __sub__(self, other: timedelta) -> Date: ...
1239 @overload
1240 def __sub__(self, other: date) -> timedelta: ...
1242 def __sub__(self, other: timedelta | date) -> Date | timedelta:
1243 if PY38:
1244 result = date(self.year, self.month, self.day).__sub__(other)
1245 else:
1246 result = super().__sub__(other) # type: ignore[operator]
1248 if isinstance(result, date):
1249 result = self._new(result)
1251 return result
1253 def replace(self, *args: object, **kwargs: object) -> Date:
1254 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type]
1256 def _new(self, result: date) -> Date:
1257 raw = result.isoformat()
1259 return Date(result.year, result.month, result.day, self._trivia, raw)
1261 def _getstate(self, protocol: int = 3) -> tuple[int, int, int, Trivia, str]:
1262 return (self.year, self.month, self.day, self._trivia, self._raw)
1265class Time(Item, time):
1266 """
1267 A time literal.
1268 """
1270 def __new__(
1271 cls,
1272 hour: int,
1273 minute: int,
1274 second: int,
1275 microsecond: int,
1276 tzinfo: tzinfo | None,
1277 trivia: Trivia | None = None,
1278 raw: str = "",
1279 ) -> Time:
1280 return time.__new__(cls, hour, minute, second, microsecond, tzinfo)
1282 def __init__(
1283 self,
1284 hour: int,
1285 minute: int,
1286 second: int,
1287 microsecond: int,
1288 tzinfo: tzinfo | None,
1289 trivia: Trivia | None = None,
1290 raw: str = "",
1291 ) -> None:
1292 super().__init__(trivia or Trivia())
1294 self._raw = raw
1296 def unwrap(self) -> time:
1297 (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
1298 return time(hour, minute, second, microsecond, tzinfo)
1300 @property
1301 def discriminant(self) -> int:
1302 return 7
1304 @property
1305 def value(self) -> time:
1306 return self
1308 def as_string(self) -> str:
1309 return self._raw
1311 def replace(self, *args: object, **kwargs: object) -> Time:
1312 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type]
1314 def _new(self, result: time) -> Time:
1315 raw = result.isoformat()
1317 return Time(
1318 result.hour,
1319 result.minute,
1320 result.second,
1321 result.microsecond,
1322 result.tzinfo,
1323 self._trivia,
1324 raw,
1325 )
1327 def _getstate(
1328 self, protocol: int = 3
1329 ) -> tuple[int, int, int, int, tzinfo | None, Trivia, str]:
1330 return (
1331 self.hour,
1332 self.minute,
1333 self.second,
1334 self.microsecond,
1335 self.tzinfo,
1336 self._trivia,
1337 self._raw,
1338 )
1341class _ArrayItemGroup:
1342 __slots__ = ("comma", "comment", "indent", "value")
1344 def __init__(
1345 self,
1346 value: Item | None = None,
1347 indent: Whitespace | None = None,
1348 comma: Whitespace | None = None,
1349 comment: Comment | None = None,
1350 ) -> None:
1351 self.value = value
1352 self.indent = indent
1353 self.comma = comma
1354 self.comment = comment
1356 def __iter__(self) -> Iterator[Item]:
1357 return (
1358 x
1359 for x in (self.indent, self.value, self.comma, self.comment)
1360 if x is not None
1361 )
1363 def __repr__(self) -> str:
1364 return repr(tuple(self))
1366 def is_whitespace(self) -> bool:
1367 return self.value is None and self.comment is None
1369 def __bool__(self) -> bool:
1370 try:
1371 next(iter(self))
1372 except StopIteration:
1373 return False
1374 return True
1377class Array(Item, _CustomList): # type: ignore[type-arg]
1378 """
1379 An array literal
1380 """
1382 def __init__(
1383 self, value: list[Item], trivia: Trivia, multiline: bool = False
1384 ) -> None:
1385 super().__init__(trivia)
1386 list.__init__(
1387 self,
1388 [v for v in value if not isinstance(v, (Whitespace, Comment, Null))],
1389 )
1390 self._index_map: dict[int, int] = {}
1391 self._value = self._group_values(value)
1392 self._multiline = multiline
1393 self._reindex()
1395 def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
1396 """Group the values into (indent, value, comma, comment) tuples"""
1397 groups = []
1398 this_group = _ArrayItemGroup()
1399 start_new_group = False
1400 for item in value:
1401 if isinstance(item, Whitespace):
1402 if "," not in item.s or start_new_group:
1403 groups.append(this_group)
1404 this_group = _ArrayItemGroup(indent=item)
1405 start_new_group = False
1406 else:
1407 if this_group.value is None:
1408 # when comma is met and no value is provided, add a dummy Null
1409 this_group.value = Null()
1410 this_group.comma = item
1411 elif isinstance(item, Comment):
1412 if this_group.value is None:
1413 this_group.value = Null()
1414 this_group.comment = item
1415 # Comments are the last item in a group.
1416 start_new_group = True
1417 elif this_group.value is None:
1418 this_group.value = item
1419 else:
1420 groups.append(this_group)
1421 this_group = _ArrayItemGroup(value=item)
1422 groups.append(this_group)
1423 return [group for group in groups if group]
1425 def unwrap(self) -> list[Any]:
1426 unwrapped = []
1427 for v in self:
1428 if hasattr(v, "unwrap"):
1429 unwrapped.append(v.unwrap())
1430 else:
1431 unwrapped.append(v)
1432 return unwrapped
1434 @property
1435 def discriminant(self) -> int:
1436 return 8
1438 @property
1439 def value(self) -> list[Item]:
1440 return self
1442 def _iter_items(self) -> Iterator[Item]:
1443 for v in self._value:
1444 yield from v
1446 def multiline(self, multiline: bool) -> Array:
1447 """Change the array to display in multiline or not.
1449 :Example:
1451 >>> a = item([1, 2, 3])
1452 >>> print(a.as_string())
1453 [1, 2, 3]
1454 >>> print(a.multiline(True).as_string())
1455 [
1456 1,
1457 2,
1458 3,
1459 ]
1460 """
1461 self._multiline = multiline
1463 return self
1465 def as_string(self) -> str:
1466 if not self._multiline or not self._value:
1467 return f"[{''.join(v.as_string() for v in self._iter_items())}]"
1469 s = "[\n"
1470 s += "".join(
1471 self.trivia.indent
1472 + " " * 4
1473 + v.value.as_string()
1474 + ("," if not isinstance(v.value, Null) else "")
1475 + (v.comment.as_string() if v.comment is not None else "")
1476 + "\n"
1477 for v in self._value
1478 if v.value is not None
1479 )
1480 s += self.trivia.indent + "]"
1482 return s
1484 def _reindex(self) -> None:
1485 self._index_map.clear()
1486 index = 0
1487 for i, v in enumerate(self._value):
1488 if v.value is None or isinstance(v.value, Null):
1489 continue
1490 self._index_map[index] = i
1491 index += 1
1493 def add_line(
1494 self,
1495 *items: Any,
1496 indent: str = " ",
1497 comment: str | None = None,
1498 add_comma: bool = True,
1499 newline: bool = True,
1500 ) -> None:
1501 """Add multiple items in a line to control the format precisely.
1502 When add_comma is True, only accept actual values and
1503 ", " will be added between values automatically.
1505 :Example:
1507 >>> a = array()
1508 >>> a.add_line(1, 2, 3)
1509 >>> a.add_line(4, 5, 6)
1510 >>> a.add_line(indent="")
1511 >>> print(a.as_string())
1512 [
1513 1, 2, 3,
1514 4, 5, 6,
1515 ]
1516 """
1517 if comment and ("\n" in comment or "\r" in comment):
1518 raise ValueError("Comment cannot contain line breaks")
1519 new_values: list[Item] = []
1520 first_indent = f"\n{indent}" if newline else indent
1521 if first_indent:
1522 new_values.append(Whitespace(first_indent))
1523 whitespace = ""
1524 data_values = []
1525 for i, el in enumerate(items):
1526 it = item(el, _parent=self)
1527 if isinstance(it, Comment) or (add_comma and isinstance(el, Whitespace)):
1528 raise ValueError(f"item type {type(it)} is not allowed in add_line")
1529 if not isinstance(it, Whitespace):
1530 if whitespace:
1531 new_values.append(Whitespace(whitespace))
1532 whitespace = ""
1533 new_values.append(it)
1534 data_values.append(it.value)
1535 if add_comma:
1536 new_values.append(Whitespace(","))
1537 if i != len(items) - 1:
1538 new_values.append(Whitespace(" "))
1539 elif "," not in it.s:
1540 whitespace += it.s
1541 else:
1542 new_values.append(it)
1543 if whitespace:
1544 new_values.append(Whitespace(whitespace))
1545 if comment:
1546 indent = " " if items else ""
1547 new_values.append(
1548 Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
1549 )
1550 list.extend(self, data_values)
1551 if len(self._value) > 0:
1552 last_item = self._value[-1]
1553 last_value_item = next(
1554 (
1555 v
1556 for v in self._value[::-1]
1557 if v.value is not None and not isinstance(v.value, Null)
1558 ),
1559 None,
1560 )
1561 if last_value_item is not None:
1562 last_value_item.comma = Whitespace(",")
1563 if last_item.is_whitespace():
1564 self._value[-1:-1] = self._group_values(new_values)
1565 else:
1566 self._value.extend(self._group_values(new_values))
1567 else:
1568 self._value.extend(self._group_values(new_values))
1569 self._reindex()
1571 def clear(self) -> None:
1572 """Clear the array."""
1573 list.clear(self)
1574 self._index_map.clear()
1575 self._value.clear()
1577 def __len__(self) -> int:
1578 return list.__len__(self)
1580 def item(self, index: int) -> Item:
1581 return list.__getitem__(self, index) # type: ignore[no-any-return]
1583 def __getitem__(self, key: int | slice) -> Any: # type: ignore[override]
1584 return list.__getitem__(self, key)
1586 def __setitem__(self, key: int | slice, value: Any) -> None: # type: ignore[override]
1587 it = item(value, _parent=self)
1588 list.__setitem__(self, key, it)
1589 if isinstance(key, slice):
1590 raise ValueError("slice assignment is not supported")
1591 if key < 0:
1592 key += len(self)
1593 self._value[self._index_map[key]].value = it
1595 def insert(self, pos: int, value: Any) -> None: # type: ignore[override]
1596 it = item(value, _parent=self)
1597 length = len(self)
1598 if not isinstance(it, (Comment, Whitespace)):
1599 list.insert(self, pos, it)
1600 if pos < 0:
1601 pos += length
1602 if pos < 0:
1603 pos = 0
1605 idx = 0 # insert position of the self._value list
1606 default_indent = " "
1607 if pos < length:
1608 try:
1609 idx = self._index_map[pos]
1610 except KeyError as e:
1611 raise IndexError("list index out of range") from e
1612 else:
1613 idx = len(self._value)
1614 if idx >= 1 and self._value[idx - 1].is_whitespace():
1615 # The last item is a pure whitespace(\n ), insert before it
1616 idx -= 1
1617 _indent = self._value[idx].indent
1618 if _indent is not None and "\n" in _indent.s:
1619 default_indent = "\n "
1620 indent: Whitespace | None = None
1621 comma: Whitespace | None = Whitespace(",") if pos < length else None
1622 if idx < len(self._value) and not self._value[idx].is_whitespace():
1623 # Prefer to copy the indentation from the item after
1624 indent = self._value[idx].indent
1625 if idx > 0:
1626 last_item = self._value[idx - 1]
1627 if indent is None:
1628 indent = last_item.indent
1629 if not isinstance(last_item.value, Null) and "\n" in default_indent:
1630 # Copy the comma from the last item if 1) it contains a value and
1631 # 2) the array is multiline
1632 comma = last_item.comma
1633 comma_in_indent = indent is not None and "," in indent.s
1634 if (
1635 last_item.comma is None
1636 and not isinstance(last_item.value, Null)
1637 and not comma_in_indent
1638 ):
1639 # Add comma to the last item to separate it from the following
1640 # items, unless the new item's indent already starts with a
1641 # comma (comma-first style).
1642 last_item.comma = Whitespace(",")
1643 if indent is not None and "," in indent.s:
1644 # Comma-first style: the item that will follow the new one brings
1645 # its own leading comma, so the new item must not add a trailing
1646 # comma of its own.
1647 comma = None
1648 if indent is None and (idx > 0 or "\n" in default_indent):
1649 # apply default indent if it isn't the first item or the array is multiline.
1650 indent = Whitespace(default_indent)
1651 new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
1652 self._value.insert(idx, new_item)
1653 self._reindex()
1655 def __delitem__(self, key: int | slice) -> None: # type: ignore[override]
1656 length = len(self)
1657 list.__delitem__(self, key)
1659 if isinstance(key, slice):
1660 indices_to_remove = list(
1661 range(key.start or 0, key.stop or length, key.step or 1)
1662 )
1663 else:
1664 indices_to_remove = [length + key if key < 0 else key]
1665 for i in sorted(indices_to_remove, reverse=True):
1666 try:
1667 idx = self._index_map[i]
1668 except KeyError as e:
1669 if not isinstance(key, slice):
1670 raise IndexError("list index out of range") from e
1671 else:
1672 group_rm = self._value[idx]
1673 del self._value[idx]
1674 if (
1675 idx == 0
1676 and len(self._value) > 0
1677 and (ind := self._value[idx].indent)
1678 and "\n" not in ind.s
1679 ):
1680 # Remove the indentation of the first item if not newline
1681 self._value[idx].indent = None
1682 comma_in_indent = (
1683 group_rm.indent is not None and "," in group_rm.indent.s
1684 )
1685 comma_in_comma = group_rm.comma is not None and "," in group_rm.comma.s
1686 if comma_in_indent and comma_in_comma:
1687 # Removed group had both commas. Add one to the next group.
1688 group = self._value[idx] if len(self._value) > idx else None
1689 if group is not None:
1690 if group.indent is None:
1691 group.indent = Whitespace(",")
1692 elif "," not in group.indent.s:
1693 # Insert the comma after the newline
1694 try:
1695 newline_index = group.indent.s.index("\n")
1696 group.indent._s = (
1697 group.indent.s[: newline_index + 1]
1698 + ","
1699 + group.indent.s[newline_index + 1 :]
1700 )
1701 except ValueError:
1702 group.indent._s = "," + group.indent.s
1703 elif not comma_in_indent and not comma_in_comma:
1704 # Removed group had no commas. Remove the next comma found.
1705 for j in range(idx, len(self._value)):
1706 group = self._value[j]
1707 if group.indent is not None and "," in group.indent.s:
1708 group.indent._s = group.indent.s.replace(",", "", 1)
1709 break
1710 if group_rm.indent is not None and "\n" in group_rm.indent.s:
1711 # Restore the removed group's newline onto the next group
1712 # if the next group does not have a newline.
1713 # i.e. the two were on the same line
1714 group = self._value[idx] if len(self._value) > idx else None
1715 if group is not None and (
1716 group.indent is None or "\n" not in group.indent.s
1717 ):
1718 group.indent = group_rm.indent
1720 if len(self._value) > 0:
1721 v = self._value[-1]
1722 if not v.is_whitespace():
1723 # remove the comma of the last item
1724 v.comma = None
1726 self._reindex()
1728 def _getstate(self, protocol: int = 3) -> tuple[list[Item], Trivia, bool]:
1729 return list(self._iter_items()), self._trivia, self._multiline
1732class AbstractTable(Item, _CustomDict): # type: ignore[type-arg]
1733 """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
1735 def __init__(self, value: container.Container, trivia: Trivia):
1736 Item.__init__(self, trivia)
1738 self._value = value
1740 for k, v in self._value.body:
1741 if k is not None:
1742 dict.__setitem__(self, k.key, v)
1744 def unwrap(self) -> dict[str, Any]:
1745 # Delegate to the inner container's unwrap, which walks its _body
1746 # directly instead of re-resolving every key through items()/__getitem__
1747 # (which rebuilds a SingleKey, and an OutOfOrderTableProxy for
1748 # out-of-order keys, per key just to throw it away).
1749 return self._value.unwrap()
1751 @property
1752 def value(self) -> container.Container:
1753 return self._value
1755 @overload
1756 def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ...
1758 @overload
1759 def append(self: AT, key: Key | str, value: Any) -> AT: ...
1761 def append(self: AT, key: Key | str | None, value: Any) -> AT:
1762 raise NotImplementedError
1764 @overload
1765 def add(self: AT, key: Comment | Whitespace) -> AT: ...
1767 @overload
1768 def add(self: AT, key: Key | str, value: Any = ...) -> AT: ...
1770 def add(
1771 self: AT, key: Key | str | Comment | Whitespace, value: Any | None = None
1772 ) -> AT:
1773 if value is None:
1774 if not isinstance(key, (Comment, Whitespace)):
1775 msg = "Non comment/whitespace items must have an associated key"
1776 raise ValueError(msg)
1778 return self.append(None, key)
1780 if isinstance(key, (Comment, Whitespace)):
1781 raise ValueError("Comment/Whitespace keys must not have a value")
1783 return self.append(key, value)
1785 def remove(self: AT, key: Key | str) -> AT:
1786 self._value.remove(key)
1788 if isinstance(key, Key):
1789 key = key.key
1791 if key is not None:
1792 dict.__delitem__(self, key)
1794 return self
1796 def item(self, key: Key | str) -> Item | OutOfOrderTableProxy:
1797 return self._value.item(key)
1799 def setdefault(self, key: Key | str, default: Any) -> Any: # type: ignore[override]
1800 super().setdefault(key, default)
1801 return self[key]
1803 def __str__(self) -> str:
1804 return str(self.value)
1806 def copy(self: AT) -> AT:
1807 return copy.copy(self)
1809 def __repr__(self) -> str:
1810 return repr(self.value)
1812 def __iter__(self) -> Iterator[str]:
1813 return iter(self._value)
1815 def __len__(self) -> int:
1816 return len(self._value)
1818 def __delitem__(self, key: Key | str) -> None:
1819 self.remove(key)
1821 def __getitem__(self, key: Key | str) -> Any:
1822 return self._value[key]
1824 def __contains__(self, key: object) -> bool:
1825 # Native membership test. The inherited ``MutableMapping.__contains__``
1826 # resolves the value via ``__getitem__`` (``self._value[key]``) -- and
1827 # builds a ``NonExistentKey`` on every absent key -- only to discard it.
1828 # Delegate straight to the inner container instead: its ``__contains__``
1829 # answers from ``_map`` while still building the ``OutOfOrderTableProxy``
1830 # for an out-of-order entry, so validation runs exactly as before.
1831 return key in self._value
1833 def __setitem__(self, key: Key | str, value: Any) -> None:
1834 if not isinstance(value, Item):
1835 value = item(value, _parent=self)
1837 is_replace = key in self
1838 self._value[key] = value
1840 if key is not None:
1841 dict.__setitem__(self, key, value)
1843 if is_replace:
1844 return
1845 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1846 if not m:
1847 return
1849 indent = m.group(1)
1851 if not isinstance(value, Whitespace):
1852 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1853 if not m:
1854 value.trivia.indent = indent
1855 else:
1856 value.trivia.indent = m.group(1) + indent + m.group(2)
1859class Table(AbstractTable):
1860 """
1861 A table literal.
1862 """
1864 def __init__(
1865 self,
1866 value: container.Container,
1867 trivia: Trivia,
1868 is_aot_element: bool,
1869 is_super_table: bool | None = None,
1870 name: str | None = None,
1871 display_name: str | None = None,
1872 ) -> None:
1873 super().__init__(value, trivia)
1875 self.name = name
1876 self.display_name = display_name
1877 self._is_aot_element = is_aot_element
1878 self._is_super_table = is_super_table
1880 @property
1881 def discriminant(self) -> int:
1882 return 9
1884 def __copy__(self) -> Table:
1885 return type(self)(
1886 self._value.copy(),
1887 self._trivia.copy(),
1888 self._is_aot_element,
1889 self._is_super_table,
1890 self.name,
1891 self.display_name,
1892 )
1894 def append(self, key: Key | str | None, _item: Any) -> Table:
1895 """
1896 Appends a (key, item) to the table.
1897 """
1898 if not isinstance(_item, Item):
1899 _item = item(_item, _parent=self)
1901 self._value.append(key, _item)
1903 if isinstance(key, Key):
1904 key = next(iter(key)).key
1905 _item = self._value[key]
1907 if key is not None:
1908 dict.__setitem__(self, key, _item)
1910 m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1911 if not m:
1912 return self
1914 indent = m.group(1)
1916 if not isinstance(_item, Whitespace):
1917 m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
1918 if not m:
1919 _item.trivia.indent = indent
1920 else:
1921 _item.trivia.indent = m.group(1) + indent + m.group(2)
1923 return self
1925 def raw_append(self, key: Key | str | None, _item: Any) -> Table:
1926 """Similar to :meth:`append` but does not copy indentation."""
1927 if not isinstance(_item, Item):
1928 _item = item(_item)
1930 self._value.append(key, _item, validate=False)
1932 if isinstance(key, Key):
1933 key = next(iter(key)).key
1934 _item = self._value[key]
1936 if key is not None:
1937 dict.__setitem__(self, key, _item)
1939 return self
1941 def is_aot_element(self) -> bool:
1942 """True if the table is the direct child of an AOT element."""
1943 return self._is_aot_element
1945 def is_super_table(self) -> bool:
1946 """A super table is the intermediate parent of a nested table as in [a.b.c].
1947 If true, it won't appear in the TOML representation."""
1948 if self._is_super_table is not None:
1949 return self._is_super_table
1950 if not self:
1951 return False
1952 # If the table has children and all children are tables, then it is a super table.
1953 for k, child in self.items():
1954 if not isinstance(k, Key):
1955 k = SingleKey(k)
1956 index = self.value._map[k]
1957 if isinstance(index, tuple):
1958 return False
1959 real_key = self.value.body[index][0]
1960 if (
1961 not isinstance(child, (Table, AoT))
1962 or real_key is None
1963 or real_key.is_dotted()
1964 ):
1965 return False
1966 return True
1968 def as_string(self) -> str:
1969 return self._value.as_string()
1971 # Helpers
1973 def indent(self, indent: int) -> Table:
1974 """Indent the table with given number of spaces."""
1975 super().indent(indent)
1977 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1978 if not m:
1979 indent_str = ""
1980 else:
1981 indent_str = m.group(1)
1983 for _, item in self._value.body:
1984 if not isinstance(item, Whitespace):
1985 item.trivia.indent = indent_str + item.trivia.indent
1987 return self
1989 def invalidate_display_name(self) -> None:
1990 """Call ``invalidate_display_name`` on the contained tables"""
1991 self.display_name = None
1993 for child in self.values():
1994 if hasattr(child, "invalidate_display_name"):
1995 child.invalidate_display_name()
1997 def _getstate(
1998 self, protocol: int = 3
1999 ) -> tuple[container.Container, Trivia, bool, bool | None, str | None, str | None]:
2000 return (
2001 self._value,
2002 self._trivia,
2003 self._is_aot_element,
2004 self._is_super_table,
2005 self.name,
2006 self.display_name,
2007 )
2010class InlineTable(AbstractTable):
2011 """
2012 An inline table literal.
2013 """
2015 def __init__(
2016 self, value: container.Container, trivia: Trivia, new: bool = False
2017 ) -> None:
2018 super().__init__(value, trivia)
2020 self._new = new
2022 @property
2023 def discriminant(self) -> int:
2024 return 10
2026 def append(self, key: Key | str | None, _item: Any) -> InlineTable:
2027 """
2028 Appends a (key, item) to the table.
2029 """
2030 if not isinstance(_item, Item):
2031 _item = item(_item, _parent=self)
2033 self._validate_child(_item)
2035 if not isinstance(_item, (Whitespace, Comment)):
2036 if not _item.trivia.indent and len(self._value) > 0 and not self._new:
2037 _item.trivia.indent = " "
2038 if _item.trivia.comment:
2039 _item.trivia.comment = ""
2041 self._value.append(key, _item)
2043 if isinstance(key, Key):
2044 key = key.key
2046 if key is not None:
2047 dict.__setitem__(self, key, _item)
2049 return self
2051 def _validate_child(self, _item: Item) -> None:
2052 if isinstance(_item, Table):
2053 raise ValueError("Inline tables cannot contain a table")
2055 def as_string(self) -> str:
2056 buf = "{"
2057 emitted_key = False
2058 needs_separator = False
2059 # Single pass over the body to precompute everything the render loop
2060 # needs, instead of rescanning the tail on every separator comma (which
2061 # was O(n^2) on large inline tables): whether any explicit-comma
2062 # whitespace is present, the index of the last real key, and the index
2063 # of the last Null (deleted) element.
2064 has_explicit_commas = False
2065 last_item_idx = None
2066 last_null_idx = -1
2067 for _i, (_k, _v) in enumerate(self._value.body):
2068 if _k is not None:
2069 last_item_idx = _i
2070 elif isinstance(_v, Whitespace) and "," in _v.s:
2071 has_explicit_commas = True
2072 if isinstance(_v, Null):
2073 last_null_idx = _i
2074 pending_separator = False
2075 # Buffer position right after the last rendered value, used to place a
2076 # deferred separator comma after the value rather than after a trailing
2077 # comment/whitespace.
2078 last_value_end = len(buf)
2079 for i, (k, v) in enumerate(self._value.body):
2080 if k is None:
2081 if isinstance(v, Whitespace) and "," in v.s:
2082 if not emitted_key:
2083 buf += v.as_string().replace(",", "", 1)
2084 continue
2086 # Equivalent to scanning body[i + 1 :] for a Null / a real
2087 # key, but O(1) using the indices precomputed above.
2088 has_following_null = last_null_idx > i
2089 has_following_key = last_item_idx is not None and last_item_idx > i
2090 if has_following_null and not has_following_key:
2091 buf += v.as_string().replace(",", "", 1)
2092 continue
2094 if pending_separator:
2095 # A previous key was removed, leaving this comma as a
2096 # duplicate of an already emitted separator. Drop it to
2097 # avoid producing an invalid ``, ,`` sequence.
2098 buf += v.as_string().replace(",", "", 1)
2099 continue
2101 pending_separator = True
2103 if i == len(self._value.body) - 1:
2104 if self._new:
2105 buf = buf.rstrip(", ")
2106 elif not has_explicit_commas or "," in v.as_string():
2107 buf = buf.rstrip(",")
2109 v_string = v.as_string()
2110 buf += v_string
2111 if "," in v_string:
2112 needs_separator = False
2114 continue
2116 if has_explicit_commas and needs_separator:
2117 # Insert the deferred separator right after the previous value,
2118 # not after any trailing comment/whitespace -- otherwise the
2119 # comma is swallowed by a trailing comment (see #512).
2120 buf = f"{buf[:last_value_end]},{buf[last_value_end:]}"
2121 needs_separator = False
2123 v_trivia_trail = v.trivia.trail.replace("\n", "")
2124 if k.is_dotted() and isinstance(v, Table):
2125 # A table materialized from a dotted key renders one
2126 # `prefix.child = value` pair per child, so that keys added
2127 # after parsing stay attached to the dotted prefix.
2128 rendered = ", ".join(self._render_dotted(k, v))
2129 else:
2130 rendered = (
2131 f"{k.as_string() + ('.' if k.is_dotted() else '')}"
2132 f"{k.sep}"
2133 f"{v.as_string()}"
2134 )
2135 buf += f"{v.trivia.indent}{rendered}"
2136 last_value_end = len(buf)
2137 buf += f"{v.trivia.comment}{v_trivia_trail}"
2138 emitted_key = True
2139 pending_separator = False
2140 if has_explicit_commas:
2141 needs_separator = True
2143 if (
2144 not has_explicit_commas
2145 and last_item_idx is not None
2146 and i < last_item_idx
2147 ):
2148 buf += ","
2149 if self._new:
2150 buf += " "
2152 buf += "}"
2154 return buf
2156 def _render_dotted(self, key: Key, table: Table) -> list[str]:
2157 """Render a table materialized from a dotted key as a list of
2158 ``prefix.child = value`` strings, recursing into nested dotted
2159 children."""
2160 prefix = f"{key.as_string()}.{key.sep}"
2161 parts = []
2162 for k, v in table.value.body:
2163 if k is None:
2164 continue
2165 if isinstance(v, Table):
2166 parts.extend(f"{prefix}{sub}" for sub in self._render_dotted(k, v))
2167 else:
2168 trail = v.trivia.trail.replace("\n", "")
2169 parts.append(
2170 f"{prefix}{v.trivia.indent}{k.as_string()}{k.sep}{v.as_string()}"
2171 f"{v.trivia.comment_ws}{v.trivia.comment}{trail}"
2172 )
2173 return parts
2175 def __setitem__(self, key: Key | str, value: Any) -> None:
2176 if hasattr(value, "trivia") and value.trivia.comment:
2177 value.trivia.comment = ""
2178 if not isinstance(value, Item):
2179 value = item(value, _parent=self)
2180 self._validate_child(value)
2181 super().__setitem__(key, value)
2183 def __copy__(self) -> InlineTable:
2184 return type(self)(self._value.copy(), self._trivia.copy(), self._new)
2186 def _getstate(self, protocol: int = 3) -> tuple[container.Container, Trivia]:
2187 return (self._value, self._trivia)
2190class String(str, Item):
2191 """
2192 A string literal.
2193 """
2195 def __new__(
2196 cls, t: StringType, value: str, original: str, trivia: Trivia
2197 ) -> String:
2198 return super().__new__(cls, value)
2200 def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
2201 super().__init__(trivia)
2203 self._t = t
2204 self._original = original
2206 def unwrap(self) -> str:
2207 return str(self)
2209 @property
2210 def discriminant(self) -> int:
2211 return 11
2213 @property
2214 def value(self) -> str:
2215 return self
2217 def as_string(self) -> str:
2218 return f"{self._t.value}{decode(self._original)}{self._t.value}"
2220 @property
2221 def type(self) -> StringType:
2222 return self._t
2224 def __add__(self, other: str) -> String:
2225 result = super().__add__(other)
2226 original = self._original + getattr(other, "_original", other)
2228 return self._new(result, original)
2230 def _new(self, result: str, original: str) -> String:
2231 return String(self._t, result, original, self._trivia)
2233 def _getstate(self, protocol: int = 3) -> tuple[StringType, str, str, Trivia]:
2234 return self._t, str(self), self._original, self._trivia
2236 @classmethod
2237 def from_raw(
2238 cls, value: str, type_: StringType = StringType.SLB, escape: bool = True
2239 ) -> String:
2240 value = decode(value)
2242 invalid = type_.invalid_sequences
2243 if any(c in value for c in invalid):
2244 raise InvalidStringError(value, invalid, type_.value)
2246 escaped = type_.escaped_sequences
2247 string_value = escape_string(value, escaped) if escape and escaped else value
2249 if type_.is_multiline() and string_value[:1] in ("\n", "\r"):
2250 # A newline immediately following the opening delimiter of a
2251 # multiline string is trimmed by the parser, so a value that
2252 # starts with a newline would otherwise lose it on round-trip.
2253 # Emit an extra leading newline (the trimmed one) to preserve it.
2254 string_value = "\n" + string_value
2256 return cls(type_, decode(value), string_value, Trivia())
2259class AoT(Item, _CustomList): # type: ignore[type-arg]
2260 """
2261 An array of table literal
2262 """
2264 def __init__(
2265 self, body: list[Table], name: str | None = None, parsed: bool = False
2266 ) -> None:
2267 self.name = name
2268 self._body: list[Table] = []
2269 self._parsed = parsed
2271 super().__init__(Trivia(trail=""))
2273 for table in body:
2274 self.append(table)
2276 def unwrap(self) -> list[dict[str, Any]]:
2277 unwrapped = []
2278 for t in self._body:
2279 if hasattr(t, "unwrap"):
2280 unwrapped.append(t.unwrap())
2281 else:
2282 unwrapped.append(t)
2283 return unwrapped
2285 @property
2286 def body(self) -> list[Table]:
2287 return self._body
2289 @property
2290 def discriminant(self) -> int:
2291 return 12
2293 @property
2294 def value(self) -> list[dict[str, Any]]:
2295 return [v.value for v in self._body]
2297 def __len__(self) -> int:
2298 return len(self._body)
2300 @overload # type: ignore[override]
2301 def __getitem__(self, key: slice) -> list[Table]: ...
2303 @overload
2304 def __getitem__(self, key: int) -> Table: ...
2306 def __getitem__(self, key: int | slice) -> Table | list[Table]:
2307 return self._body[key]
2309 def __setitem__(self, key: slice | int, value: Any) -> None: # type: ignore[override]
2310 self._body[key] = item(value, _parent=self)
2312 def __delitem__(self, key: slice | int) -> None: # type: ignore[override]
2313 del self._body[key]
2314 list.__delitem__(self, key)
2316 def insert(self, index: int, value: dict[str, Any]) -> None: # type: ignore[override]
2317 value = item(value, _parent=self)
2318 if not isinstance(value, Table):
2319 raise ValueError(f"Unsupported insert value type: {type(value)}")
2320 length = len(self)
2321 if index < 0:
2322 index += length
2323 if index < 0:
2324 index = 0
2325 elif index >= length:
2326 index = length
2327 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
2328 if m:
2329 indent = m.group(1)
2331 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
2332 if not m:
2333 value.trivia.indent = indent
2334 else:
2335 value.trivia.indent = m.group(1) + indent + m.group(2)
2336 prev_table = self._body[index - 1] if 0 < index and length else None
2337 next_table = self._body[index + 1] if index < length - 1 else None
2338 if not self._parsed:
2339 if prev_table and "\n" not in value.trivia.indent:
2340 value.trivia.indent = "\n" + value.trivia.indent
2341 if next_table and "\n" not in next_table.trivia.indent:
2342 next_table.trivia.indent = "\n" + next_table.trivia.indent
2343 self._body.insert(index, value)
2344 list.insert(self, index, value)
2346 def invalidate_display_name(self) -> None:
2347 """Call ``invalidate_display_name`` on the contained tables"""
2348 for child in self:
2349 if hasattr(child, "invalidate_display_name"):
2350 child.invalidate_display_name()
2352 def as_string(self) -> str:
2353 b = ""
2354 for table in self._body:
2355 b += table.as_string()
2357 return b
2359 def __repr__(self) -> str:
2360 return f"<AoT {self.value}>"
2362 def _getstate(self, protocol: int = 3) -> tuple[list[Table], str | None, bool]:
2363 return self._body, self.name, self._parsed
2366class Null(Item):
2367 """
2368 A null item.
2369 """
2371 def __init__(self) -> None:
2372 super().__init__(Trivia(trail=""))
2374 def unwrap(self) -> None:
2375 return None
2377 @property
2378 def discriminant(self) -> int:
2379 return -1
2381 @property
2382 def value(self) -> None:
2383 return None
2385 def as_string(self) -> str:
2386 return ""
2388 def _getstate(self, protocol: int = 3) -> tuple[()]:
2389 return ()