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 not comment.strip().startswith("#"):
504 comment = "# " + comment
506 self._trivia.comment_ws = " "
507 self._trivia.comment = comment
509 return self
511 def indent(self, indent: int) -> Item:
512 """Indent this item with given number of spaces"""
513 if self._trivia.indent.startswith("\n"):
514 self._trivia.indent = "\n" + " " * indent
515 else:
516 self._trivia.indent = " " * indent
518 return self
520 def is_boolean(self) -> bool:
521 return isinstance(self, Bool)
523 def is_table(self) -> bool:
524 return isinstance(self, Table)
526 def is_inline_table(self) -> bool:
527 return isinstance(self, InlineTable)
529 def is_aot(self) -> bool:
530 return isinstance(self, AoT)
532 def _getstate(self, protocol: int = 3) -> tuple[object, ...]:
533 return (self._trivia,)
535 def __reduce__(self) -> tuple[type, tuple[object, ...]]:
536 return self.__reduce_ex__(2)
538 def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[object, ...]]: # type: ignore[override]
539 return self.__class__, self._getstate(protocol)
541 def __getitem__(self, key: Key | str | int) -> Any:
542 raise TypeError(f"{type(self).__name__} does not support item access")
544 def __setitem__(self, key: Key | str | int, value: Any) -> None:
545 raise TypeError(f"{type(self).__name__} does not support item assignment")
547 def __delitem__(self, key: Key | str | int) -> None:
548 raise TypeError(f"{type(self).__name__} does not support item deletion")
551class Whitespace(Item):
552 """
553 A whitespace literal.
554 """
556 def __init__(self, s: str, fixed: bool = False) -> None:
557 self._s = s
558 self._fixed = fixed
560 @property
561 def s(self) -> str:
562 return self._s
564 @property
565 def value(self) -> str:
566 """The wrapped string of the whitespace"""
567 return self._s
569 @property
570 def trivia(self) -> Trivia:
571 raise RuntimeError("Called trivia on a Whitespace variant.")
573 @property
574 def discriminant(self) -> int:
575 return 0
577 def is_fixed(self) -> bool:
578 """If the whitespace is fixed, it can't be merged or discarded from the output."""
579 return self._fixed
581 def as_string(self) -> str:
582 return self._s
584 def __repr__(self) -> str:
585 return f"<{self.__class__.__name__} {self._s!r}>"
587 def _getstate(self, protocol: int = 3) -> tuple[str, bool]:
588 return self._s, self._fixed
591class Comment(Item):
592 """
593 A comment literal.
594 """
596 @property
597 def discriminant(self) -> int:
598 return 1
600 def as_string(self) -> str:
601 return (
602 f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
603 )
605 def __str__(self) -> str:
606 return f"{self._trivia.indent}{decode(self._trivia.comment)}"
609class Integer(Item, _CustomInt):
610 """
611 An integer literal.
612 """
614 def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
615 return int.__new__(cls, value)
617 def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
618 super().__init__(trivia)
619 self._original = value
620 self._raw = raw
621 self._sign = False
623 if re.match(r"^[+\-]\d+$", raw):
624 self._sign = True
626 def unwrap(self) -> int:
627 return self._original
629 __int__ = unwrap
631 def __hash__(self) -> int:
632 return hash(self.unwrap())
634 @property
635 def discriminant(self) -> int:
636 return 2
638 @property
639 def value(self) -> int:
640 """The wrapped integer value"""
641 return self
643 def as_string(self) -> str:
644 return self._raw
646 def _new(self, result: int) -> Integer:
647 raw = str(result)
648 if self._sign and result >= 0:
649 raw = f"+{raw}"
651 return Integer(result, self._trivia, raw)
653 def _getstate(self, protocol: int = 3) -> tuple[int, Trivia, str]:
654 return int(self), self._trivia, self._raw
656 # int methods — explicit typed wrappers
657 def __abs__(self) -> Integer:
658 return self._new(int.__abs__(self))
660 def __add__(self, other: object) -> Integer:
661 result = int.__add__(self, other) # type: ignore[operator]
662 if result is NotImplemented:
663 return result # type: ignore[return-value]
664 return self._new(result)
666 def __and__(self, other: object) -> Integer:
667 result = int.__and__(self, other) # type: ignore[operator]
668 if result is NotImplemented:
669 return result # type: ignore[return-value]
670 return self._new(result)
672 def __ceil__(self) -> Integer:
673 return self._new(int.__ceil__(self))
675 __eq__ = int.__eq__
677 def __floor__(self) -> Integer:
678 return self._new(int.__floor__(self))
680 def __floordiv__(self, other: object) -> Integer:
681 result = int.__floordiv__(self, other) # type: ignore[operator]
682 if result is NotImplemented:
683 return result # type: ignore[return-value]
684 return self._new(result)
686 def __invert__(self) -> Integer:
687 return self._new(int.__invert__(self))
689 __le__ = int.__le__
691 def __lshift__(self, other: object) -> Integer:
692 result = int.__lshift__(self, other) # type: ignore[operator]
693 if result is NotImplemented:
694 return result # type: ignore[return-value]
695 return self._new(result)
697 __lt__ = int.__lt__
699 def __mod__(self, other: object) -> Integer:
700 result = int.__mod__(self, other) # type: ignore[operator]
701 if result is NotImplemented:
702 return result # type: ignore[return-value]
703 return self._new(result)
705 def __mul__(self, other: object) -> Integer:
706 result = int.__mul__(self, other) # type: ignore[operator]
707 if result is NotImplemented:
708 return result # type: ignore[return-value]
709 return self._new(result)
711 def __neg__(self) -> Integer:
712 return self._new(int.__neg__(self))
714 def __or__(self, other: object) -> Integer:
715 result = int.__or__(self, other) # type: ignore[operator]
716 if result is NotImplemented:
717 return result # type: ignore[return-value]
718 return self._new(result)
720 def __pos__(self) -> Integer:
721 return self._new(int.__pos__(self))
723 def __pow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[override]
724 result = (
725 int.__pow__(self, other) if mod is None else int.__pow__(self, other, mod)
726 )
727 return self._new(result)
729 def __radd__(self, other: object) -> Integer:
730 result = int.__radd__(self, other) # type: ignore[operator]
731 if result is NotImplemented:
732 return result # type: ignore[return-value]
733 return self._new(result)
735 def __rand__(self, other: object) -> Integer:
736 result = int.__rand__(self, other) # type: ignore[operator]
737 if result is NotImplemented:
738 return result # type: ignore[return-value]
739 return self._new(result)
741 def __rfloordiv__(self, other: object) -> Integer:
742 result = int.__rfloordiv__(self, other) # type: ignore[operator]
743 if result is NotImplemented:
744 return result # type: ignore[return-value]
745 return self._new(result)
747 def __rlshift__(self, other: object) -> Integer:
748 result = int.__rlshift__(self, other) # type: ignore[operator]
749 if result is NotImplemented:
750 return result # type: ignore[return-value]
751 return self._new(result)
753 def __rmod__(self, other: object) -> Integer:
754 result = int.__rmod__(self, other) # type: ignore[operator]
755 if result is NotImplemented:
756 return result # type: ignore[return-value]
757 return self._new(result)
759 def __rmul__(self, other: object) -> Integer:
760 result = int.__rmul__(self, other) # type: ignore[operator]
761 if result is NotImplemented:
762 return result # type: ignore[return-value]
763 return self._new(result)
765 def __ror__(self, other: object) -> Integer:
766 result = int.__ror__(self, other) # type: ignore[operator]
767 if result is NotImplemented:
768 return result # type: ignore[return-value]
769 return self._new(result)
771 def __round__(self, ndigits: int = 0) -> Integer: # type: ignore[override]
772 return self._new(int.__round__(self, ndigits))
774 def __rpow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[misc]
775 result = (
776 int.__rpow__(self, other) if mod is None else int.__rpow__(self, other, mod)
777 )
778 return self._new(result)
780 def __rrshift__(self, other: object) -> Integer:
781 result = int.__rrshift__(self, other) # type: ignore[operator]
782 if result is NotImplemented:
783 return result # type: ignore[return-value]
784 return self._new(result)
786 def __rshift__(self, other: object) -> Integer:
787 result = int.__rshift__(self, other) # type: ignore[operator]
788 if result is NotImplemented:
789 return result # type: ignore[return-value]
790 return self._new(result)
792 def __rxor__(self, other: object) -> Integer:
793 result = int.__rxor__(self, other) # type: ignore[operator]
794 if result is NotImplemented:
795 return result # type: ignore[return-value]
796 return self._new(result)
798 def __sub__(self, other: object) -> Integer:
799 result = int.__sub__(self, other) # type: ignore[operator]
800 if result is NotImplemented:
801 return result # type: ignore[return-value]
802 return self._new(result)
804 def __rsub__(self, other: object) -> Integer:
805 result = int.__rsub__(self, other) # type: ignore[operator]
806 if result is NotImplemented:
807 return result # type: ignore[return-value]
808 return self._new(result)
810 def __trunc__(self) -> Integer:
811 return self._new(int.__trunc__(self))
813 def __xor__(self, other: object) -> Integer:
814 result = int.__xor__(self, other) # type: ignore[operator]
815 if result is NotImplemented:
816 return result # type: ignore[return-value]
817 return self._new(result)
819 def __rtruediv__(self, other: object) -> Float:
820 result = int.__rtruediv__(self, other) # type: ignore[operator]
821 if result is NotImplemented:
822 return result # type: ignore[return-value]
823 return Float._new(self, result) # type: ignore[arg-type]
825 def __truediv__(self, other: object) -> Float:
826 result = int.__truediv__(self, other) # type: ignore[operator]
827 if result is NotImplemented:
828 return result # type: ignore[return-value]
829 return Float._new(self, result) # type: ignore[arg-type]
832class Float(Item, _CustomFloat):
833 """
834 A float literal.
835 """
837 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
838 return float.__new__(cls, value)
840 def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
841 super().__init__(trivia)
842 self._original = value
843 self._raw = raw
844 self._sign = False
846 if re.match(r"^[+\-].+$", raw):
847 self._sign = True
849 def unwrap(self) -> float:
850 return self._original
852 __float__ = unwrap
854 def __hash__(self) -> int:
855 return hash(self.unwrap())
857 @property
858 def discriminant(self) -> int:
859 return 3
861 @property
862 def value(self) -> float:
863 """The wrapped float value"""
864 return self
866 def as_string(self) -> str:
867 return self._raw
869 def _new(self, result: float) -> Float:
870 raw = str(result)
872 if self._sign and result >= 0:
873 raw = f"+{raw}"
875 return Float(result, self._trivia, raw)
877 def _getstate(self, protocol: int = 3) -> tuple[float, Trivia, str]:
878 return float(self), self._trivia, self._raw
880 # float methods — explicit typed wrappers
881 def __abs__(self) -> Float:
882 return self._new(float.__abs__(self))
884 def __add__(self, other: object) -> Float:
885 result = float.__add__(self, other) # type: ignore[operator]
886 if result is NotImplemented:
887 return result # type: ignore[return-value]
888 return self._new(result)
890 __eq__ = float.__eq__
892 def __floordiv__(self, other: object) -> Float:
893 result = float.__floordiv__(self, other) # type: ignore[operator]
894 if result is NotImplemented:
895 return result # type: ignore[return-value]
896 return self._new(result)
898 __le__ = float.__le__
899 __lt__ = float.__lt__
901 def __mod__(self, other: object) -> Float:
902 result = float.__mod__(self, other) # type: ignore[operator]
903 if result is NotImplemented:
904 return result # type: ignore[return-value]
905 return self._new(result)
907 def __mul__(self, other: object) -> Float:
908 result = float.__mul__(self, other) # type: ignore[operator]
909 if result is NotImplemented:
910 return result # type: ignore[return-value]
911 return self._new(result)
913 def __neg__(self) -> Float:
914 return self._new(float.__neg__(self))
916 def __pos__(self) -> Float:
917 return self._new(float.__pos__(self))
919 def __pow__(self, other: object, mod: None = None) -> Float:
920 result = float.__pow__(self, other) # type: ignore[operator]
921 if result is NotImplemented:
922 return result # type: ignore[no-any-return]
923 return self._new(result)
925 def __radd__(self, other: object) -> Float:
926 result = float.__radd__(self, other) # type: ignore[operator]
927 if result is NotImplemented:
928 return result # type: ignore[return-value]
929 return self._new(result)
931 def __rfloordiv__(self, other: object) -> Float:
932 result = float.__rfloordiv__(self, other) # type: ignore[operator]
933 if result is NotImplemented:
934 return result # type: ignore[return-value]
935 return self._new(result)
937 def __rmod__(self, other: object) -> Float:
938 result = float.__rmod__(self, other) # type: ignore[operator]
939 if result is NotImplemented:
940 return result # type: ignore[return-value]
941 return self._new(result)
943 def __rmul__(self, other: object) -> Float:
944 result = float.__rmul__(self, other) # type: ignore[operator]
945 if result is NotImplemented:
946 return result # type: ignore[return-value]
947 return self._new(result)
949 def __round__(self, ndigits: int = 0) -> Float: # type: ignore[override]
950 return self._new(float.__round__(self, ndigits))
952 def __rpow__(self, other: object, mod: None = None) -> Float:
953 result = float.__rpow__(self, other) # type: ignore[operator]
954 if result is NotImplemented:
955 return result # type: ignore[no-any-return]
956 return self._new(result)
958 def __rtruediv__(self, other: object) -> Float:
959 result = float.__rtruediv__(self, other) # type: ignore[operator]
960 if result is NotImplemented:
961 return result # type: ignore[return-value]
962 return self._new(result)
964 def __truediv__(self, other: object) -> Float:
965 result = float.__truediv__(self, other) # type: ignore[operator]
966 if result is NotImplemented:
967 return result # type: ignore[return-value]
968 return self._new(result)
970 def __sub__(self, other: object) -> Float:
971 result = float.__sub__(self, other) # type: ignore[operator]
972 if result is NotImplemented:
973 return result # type: ignore[return-value]
974 return self._new(result)
976 def __rsub__(self, other: object) -> Float:
977 result = float.__rsub__(self, other) # type: ignore[operator]
978 if result is NotImplemented:
979 return result # type: ignore[return-value]
980 return self._new(result)
982 __trunc__ = float.__trunc__
983 __ceil__ = float.__ceil__
984 __floor__ = float.__floor__
987class Bool(Item):
988 """
989 A boolean literal.
990 """
992 def __init__(self, t: int | BoolType, trivia: Trivia) -> None:
993 super().__init__(trivia)
995 self._value = bool(t)
997 def unwrap(self) -> bool:
998 return bool(self)
1000 @property
1001 def discriminant(self) -> int:
1002 return 4
1004 @property
1005 def value(self) -> bool:
1006 """The wrapped boolean value"""
1007 return self._value
1009 def as_string(self) -> str:
1010 return str(self._value).lower()
1012 def _getstate(self, protocol: int = 3) -> tuple[bool, Trivia]:
1013 return self._value, self._trivia
1015 def __bool__(self) -> bool:
1016 return self._value
1018 __nonzero__ = __bool__
1020 def __eq__(self, other: object) -> bool:
1021 if not isinstance(other, bool):
1022 return NotImplemented
1024 return other == self._value
1026 def __hash__(self) -> int:
1027 return hash(self._value)
1029 def __repr__(self) -> str:
1030 return repr(self._value)
1033class DateTime(Item, datetime):
1034 """
1035 A datetime literal.
1036 """
1038 def __new__(
1039 cls,
1040 year: int,
1041 month: int,
1042 day: int,
1043 hour: int,
1044 minute: int,
1045 second: int,
1046 microsecond: int,
1047 tzinfo: tzinfo | None,
1048 trivia: Trivia | None = None,
1049 raw: str | None = None,
1050 **kwargs: object,
1051 ) -> DateTime:
1052 return datetime.__new__(
1053 cls,
1054 year,
1055 month,
1056 day,
1057 hour,
1058 minute,
1059 second,
1060 microsecond,
1061 tzinfo=tzinfo,
1062 )
1064 def __init__(
1065 self,
1066 year: int,
1067 month: int,
1068 day: int,
1069 hour: int,
1070 minute: int,
1071 second: int,
1072 microsecond: int,
1073 tzinfo: tzinfo | None,
1074 trivia: Trivia | None = None,
1075 raw: str | None = None,
1076 **kwargs: object,
1077 ) -> None:
1078 super().__init__(trivia or Trivia())
1080 self._raw = raw or self.isoformat()
1082 def unwrap(self) -> datetime:
1083 (
1084 year,
1085 month,
1086 day,
1087 hour,
1088 minute,
1089 second,
1090 microsecond,
1091 tzinfo,
1092 _,
1093 _,
1094 ) = self._getstate()
1095 return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
1097 @property
1098 def discriminant(self) -> int:
1099 return 5
1101 @property
1102 def value(self) -> datetime:
1103 return self
1105 def as_string(self) -> str:
1106 return self._raw
1108 def __add__(self, other: timedelta) -> DateTime:
1109 if PY38:
1110 result = datetime(
1111 self.year,
1112 self.month,
1113 self.day,
1114 self.hour,
1115 self.minute,
1116 self.second,
1117 self.microsecond,
1118 self.tzinfo,
1119 ).__add__(other)
1120 else:
1121 result = super().__add__(other)
1123 return self._new(result)
1125 @overload # type: ignore[override]
1126 def __sub__(self, other: timedelta) -> DateTime: ...
1128 @overload
1129 def __sub__(self, other: datetime) -> timedelta: ...
1131 def __sub__(self, other: timedelta | datetime) -> DateTime | timedelta:
1132 if PY38:
1133 result = datetime(
1134 self.year,
1135 self.month,
1136 self.day,
1137 self.hour,
1138 self.minute,
1139 self.second,
1140 self.microsecond,
1141 self.tzinfo,
1142 ).__sub__(other)
1143 else:
1144 result = super().__sub__(other) # type: ignore[operator]
1146 if isinstance(result, datetime):
1147 result = self._new(result)
1149 return result
1151 def replace(self, *args: object, **kwargs: object) -> DateTime:
1152 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type]
1154 def astimezone(self, tz: tzinfo) -> DateTime: # type: ignore[override]
1155 result = super().astimezone(tz)
1156 if PY38:
1157 return result
1158 return self._new(result)
1160 def _new(self, result: datetime) -> DateTime:
1161 raw = result.isoformat()
1163 return DateTime(
1164 result.year,
1165 result.month,
1166 result.day,
1167 result.hour,
1168 result.minute,
1169 result.second,
1170 result.microsecond,
1171 result.tzinfo,
1172 self._trivia,
1173 raw,
1174 )
1176 def _getstate(
1177 self, protocol: int = 3
1178 ) -> tuple[int, int, int, int, int, int, int, tzinfo | None, Trivia, str]:
1179 return (
1180 self.year,
1181 self.month,
1182 self.day,
1183 self.hour,
1184 self.minute,
1185 self.second,
1186 self.microsecond,
1187 self.tzinfo,
1188 self._trivia,
1189 self._raw,
1190 )
1193class Date(Item, date):
1194 """
1195 A date literal.
1196 """
1198 def __new__(
1199 cls,
1200 year: int,
1201 month: int,
1202 day: int,
1203 trivia: Trivia | None = None,
1204 raw: str = "",
1205 ) -> Date:
1206 return date.__new__(cls, year, month, day)
1208 def __init__(
1209 self,
1210 year: int,
1211 month: int,
1212 day: int,
1213 trivia: Trivia | None = None,
1214 raw: str = "",
1215 ) -> None:
1216 super().__init__(trivia or Trivia())
1218 self._raw = raw
1220 def unwrap(self) -> date:
1221 (year, month, day, _, _) = self._getstate()
1222 return date(year, month, day)
1224 @property
1225 def discriminant(self) -> int:
1226 return 6
1228 @property
1229 def value(self) -> date:
1230 return self
1232 def as_string(self) -> str:
1233 return self._raw
1235 def __add__(self, other: timedelta) -> Date:
1236 if PY38:
1237 result = date(self.year, self.month, self.day).__add__(other)
1238 else:
1239 result = super().__add__(other)
1241 return self._new(result)
1243 @overload # type: ignore[override]
1244 def __sub__(self, other: timedelta) -> Date: ...
1246 @overload
1247 def __sub__(self, other: date) -> timedelta: ...
1249 def __sub__(self, other: timedelta | date) -> Date | timedelta:
1250 if PY38:
1251 result = date(self.year, self.month, self.day).__sub__(other)
1252 else:
1253 result = super().__sub__(other) # type: ignore[operator]
1255 if isinstance(result, date):
1256 result = self._new(result)
1258 return result
1260 def replace(self, *args: object, **kwargs: object) -> Date:
1261 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type]
1263 def _new(self, result: date) -> Date:
1264 raw = result.isoformat()
1266 return Date(result.year, result.month, result.day, self._trivia, raw)
1268 def _getstate(self, protocol: int = 3) -> tuple[int, int, int, Trivia, str]:
1269 return (self.year, self.month, self.day, self._trivia, self._raw)
1272class Time(Item, time):
1273 """
1274 A time literal.
1275 """
1277 def __new__(
1278 cls,
1279 hour: int,
1280 minute: int,
1281 second: int,
1282 microsecond: int,
1283 tzinfo: tzinfo | None,
1284 trivia: Trivia | None = None,
1285 raw: str = "",
1286 ) -> Time:
1287 return time.__new__(cls, hour, minute, second, microsecond, tzinfo)
1289 def __init__(
1290 self,
1291 hour: int,
1292 minute: int,
1293 second: int,
1294 microsecond: int,
1295 tzinfo: tzinfo | None,
1296 trivia: Trivia | None = None,
1297 raw: str = "",
1298 ) -> None:
1299 super().__init__(trivia or Trivia())
1301 self._raw = raw
1303 def unwrap(self) -> time:
1304 (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
1305 return time(hour, minute, second, microsecond, tzinfo)
1307 @property
1308 def discriminant(self) -> int:
1309 return 7
1311 @property
1312 def value(self) -> time:
1313 return self
1315 def as_string(self) -> str:
1316 return self._raw
1318 def replace(self, *args: object, **kwargs: object) -> Time:
1319 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type]
1321 def _new(self, result: time) -> Time:
1322 raw = result.isoformat()
1324 return Time(
1325 result.hour,
1326 result.minute,
1327 result.second,
1328 result.microsecond,
1329 result.tzinfo,
1330 self._trivia,
1331 raw,
1332 )
1334 def _getstate(
1335 self, protocol: int = 3
1336 ) -> tuple[int, int, int, int, tzinfo | None, Trivia, str]:
1337 return (
1338 self.hour,
1339 self.minute,
1340 self.second,
1341 self.microsecond,
1342 self.tzinfo,
1343 self._trivia,
1344 self._raw,
1345 )
1348class _ArrayItemGroup:
1349 __slots__ = ("comma", "comment", "indent", "value")
1351 def __init__(
1352 self,
1353 value: Item | None = None,
1354 indent: Whitespace | None = None,
1355 comma: Whitespace | None = None,
1356 comment: Comment | None = None,
1357 ) -> None:
1358 self.value = value
1359 self.indent = indent
1360 self.comma = comma
1361 self.comment = comment
1363 def __iter__(self) -> Iterator[Item]:
1364 return (
1365 x
1366 for x in (self.indent, self.value, self.comma, self.comment)
1367 if x is not None
1368 )
1370 def __repr__(self) -> str:
1371 return repr(tuple(self))
1373 def is_whitespace(self) -> bool:
1374 return self.value is None and self.comment is None
1376 def __bool__(self) -> bool:
1377 try:
1378 next(iter(self))
1379 except StopIteration:
1380 return False
1381 return True
1384class Array(Item, _CustomList): # type: ignore[type-arg]
1385 """
1386 An array literal
1387 """
1389 def __init__(
1390 self, value: list[Item], trivia: Trivia, multiline: bool = False
1391 ) -> None:
1392 super().__init__(trivia)
1393 list.__init__(
1394 self,
1395 [v for v in value if not isinstance(v, (Whitespace, Comment, Null))],
1396 )
1397 self._index_map: dict[int, int] = {}
1398 self._value = self._group_values(value)
1399 self._multiline = multiline
1400 self._reindex()
1402 def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
1403 """Group the values into (indent, value, comma, comment) tuples"""
1404 groups = []
1405 this_group = _ArrayItemGroup()
1406 start_new_group = False
1407 for item in value:
1408 if isinstance(item, Whitespace):
1409 if "," not in item.s or start_new_group:
1410 groups.append(this_group)
1411 this_group = _ArrayItemGroup(indent=item)
1412 start_new_group = False
1413 else:
1414 if this_group.value is None:
1415 # when comma is met and no value is provided, add a dummy Null
1416 this_group.value = Null()
1417 this_group.comma = item
1418 elif isinstance(item, Comment):
1419 if this_group.value is None:
1420 this_group.value = Null()
1421 this_group.comment = item
1422 # Comments are the last item in a group.
1423 start_new_group = True
1424 elif this_group.value is None:
1425 this_group.value = item
1426 else:
1427 groups.append(this_group)
1428 this_group = _ArrayItemGroup(value=item)
1429 groups.append(this_group)
1430 return [group for group in groups if group]
1432 def unwrap(self) -> list[Any]:
1433 unwrapped = []
1434 for v in self:
1435 if hasattr(v, "unwrap"):
1436 unwrapped.append(v.unwrap())
1437 else:
1438 unwrapped.append(v)
1439 return unwrapped
1441 @property
1442 def discriminant(self) -> int:
1443 return 8
1445 @property
1446 def value(self) -> list[Item]:
1447 return self
1449 def _iter_items(self) -> Iterator[Item]:
1450 for v in self._value:
1451 yield from v
1453 def multiline(self, multiline: bool) -> Array:
1454 """Change the array to display in multiline or not.
1456 :Example:
1458 >>> a = item([1, 2, 3])
1459 >>> print(a.as_string())
1460 [1, 2, 3]
1461 >>> print(a.multiline(True).as_string())
1462 [
1463 1,
1464 2,
1465 3,
1466 ]
1467 """
1468 self._multiline = multiline
1470 return self
1472 def as_string(self) -> str:
1473 if not self._multiline or not self._value:
1474 return f"[{''.join(v.as_string() for v in self._iter_items())}]"
1476 s = "[\n"
1477 s += "".join(
1478 self.trivia.indent
1479 + " " * 4
1480 + v.value.as_string()
1481 + ("," if not isinstance(v.value, Null) else "")
1482 + (v.comment.as_string() if v.comment is not None else "")
1483 + "\n"
1484 for v in self._value
1485 if v.value is not None
1486 )
1487 s += self.trivia.indent + "]"
1489 return s
1491 def _reindex(self) -> None:
1492 self._index_map.clear()
1493 index = 0
1494 for i, v in enumerate(self._value):
1495 if v.value is None or isinstance(v.value, Null):
1496 continue
1497 self._index_map[index] = i
1498 index += 1
1500 def add_line(
1501 self,
1502 *items: Any,
1503 indent: str = " ",
1504 comment: str | None = None,
1505 add_comma: bool = True,
1506 newline: bool = True,
1507 ) -> None:
1508 """Add multiple items in a line to control the format precisely.
1509 When add_comma is True, only accept actual values and
1510 ", " will be added between values automatically.
1512 :Example:
1514 >>> a = array()
1515 >>> a.add_line(1, 2, 3)
1516 >>> a.add_line(4, 5, 6)
1517 >>> a.add_line(indent="")
1518 >>> print(a.as_string())
1519 [
1520 1, 2, 3,
1521 4, 5, 6,
1522 ]
1523 """
1524 new_values: list[Item] = []
1525 first_indent = f"\n{indent}" if newline else indent
1526 if first_indent:
1527 new_values.append(Whitespace(first_indent))
1528 whitespace = ""
1529 data_values = []
1530 for i, el in enumerate(items):
1531 it = item(el, _parent=self)
1532 if isinstance(it, Comment) or (add_comma and isinstance(el, Whitespace)):
1533 raise ValueError(f"item type {type(it)} is not allowed in add_line")
1534 if not isinstance(it, Whitespace):
1535 if whitespace:
1536 new_values.append(Whitespace(whitespace))
1537 whitespace = ""
1538 new_values.append(it)
1539 data_values.append(it.value)
1540 if add_comma:
1541 new_values.append(Whitespace(","))
1542 if i != len(items) - 1:
1543 new_values.append(Whitespace(" "))
1544 elif "," not in it.s:
1545 whitespace += it.s
1546 else:
1547 new_values.append(it)
1548 if whitespace:
1549 new_values.append(Whitespace(whitespace))
1550 if comment:
1551 indent = " " if items else ""
1552 new_values.append(
1553 Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
1554 )
1555 list.extend(self, data_values)
1556 if len(self._value) > 0:
1557 last_item = self._value[-1]
1558 last_value_item = next(
1559 (
1560 v
1561 for v in self._value[::-1]
1562 if v.value is not None and not isinstance(v.value, Null)
1563 ),
1564 None,
1565 )
1566 if last_value_item is not None:
1567 last_value_item.comma = Whitespace(",")
1568 if last_item.is_whitespace():
1569 self._value[-1:-1] = self._group_values(new_values)
1570 else:
1571 self._value.extend(self._group_values(new_values))
1572 else:
1573 self._value.extend(self._group_values(new_values))
1574 self._reindex()
1576 def clear(self) -> None:
1577 """Clear the array."""
1578 list.clear(self)
1579 self._index_map.clear()
1580 self._value.clear()
1582 def __len__(self) -> int:
1583 return list.__len__(self)
1585 def item(self, index: int) -> Item:
1586 return list.__getitem__(self, index) # type: ignore[no-any-return]
1588 def __getitem__(self, key: int | slice) -> Any: # type: ignore[override]
1589 return list.__getitem__(self, key)
1591 def __setitem__(self, key: int | slice, value: Any) -> None: # type: ignore[override]
1592 it = item(value, _parent=self)
1593 list.__setitem__(self, key, it)
1594 if isinstance(key, slice):
1595 raise ValueError("slice assignment is not supported")
1596 if key < 0:
1597 key += len(self)
1598 self._value[self._index_map[key]].value = it
1600 def insert(self, pos: int, value: Any) -> None: # type: ignore[override]
1601 it = item(value, _parent=self)
1602 length = len(self)
1603 if not isinstance(it, (Comment, Whitespace)):
1604 list.insert(self, pos, it)
1605 if pos < 0:
1606 pos += length
1607 if pos < 0:
1608 pos = 0
1610 idx = 0 # insert position of the self._value list
1611 default_indent = " "
1612 if pos < length:
1613 try:
1614 idx = self._index_map[pos]
1615 except KeyError as e:
1616 raise IndexError("list index out of range") from e
1617 else:
1618 idx = len(self._value)
1619 if idx >= 1 and self._value[idx - 1].is_whitespace():
1620 # The last item is a pure whitespace(\n ), insert before it
1621 idx -= 1
1622 _indent = self._value[idx].indent
1623 if _indent is not None and "\n" in _indent.s:
1624 default_indent = "\n "
1625 indent: Whitespace | None = None
1626 comma: Whitespace | None = Whitespace(",") if pos < length else None
1627 if idx < len(self._value) and not self._value[idx].is_whitespace():
1628 # Prefer to copy the indentation from the item after
1629 indent = self._value[idx].indent
1630 if idx > 0:
1631 last_item = self._value[idx - 1]
1632 if indent is None:
1633 indent = last_item.indent
1634 if not isinstance(last_item.value, Null) and "\n" in default_indent:
1635 # Copy the comma from the last item if 1) it contains a value and
1636 # 2) the array is multiline
1637 comma = last_item.comma
1638 comma_in_indent = indent is not None and "," in indent.s
1639 if (
1640 last_item.comma is None
1641 and not isinstance(last_item.value, Null)
1642 and not comma_in_indent
1643 ):
1644 # Add comma to the last item to separate it from the following
1645 # items, unless the new item's indent already starts with a
1646 # comma (comma-first style).
1647 last_item.comma = Whitespace(",")
1648 if indent is not None and "," in indent.s:
1649 # Comma-first style: the item that will follow the new one brings
1650 # its own leading comma, so the new item must not add a trailing
1651 # comma of its own.
1652 comma = None
1653 if indent is None and (idx > 0 or "\n" in default_indent):
1654 # apply default indent if it isn't the first item or the array is multiline.
1655 indent = Whitespace(default_indent)
1656 new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
1657 self._value.insert(idx, new_item)
1658 self._reindex()
1660 def __delitem__(self, key: int | slice) -> None: # type: ignore[override]
1661 length = len(self)
1662 list.__delitem__(self, key)
1664 if isinstance(key, slice):
1665 indices_to_remove = list(
1666 range(key.start or 0, key.stop or length, key.step or 1)
1667 )
1668 else:
1669 indices_to_remove = [length + key if key < 0 else key]
1670 for i in sorted(indices_to_remove, reverse=True):
1671 try:
1672 idx = self._index_map[i]
1673 except KeyError as e:
1674 if not isinstance(key, slice):
1675 raise IndexError("list index out of range") from e
1676 else:
1677 group_rm = self._value[idx]
1678 del self._value[idx]
1679 if (
1680 idx == 0
1681 and len(self._value) > 0
1682 and (ind := self._value[idx].indent)
1683 and "\n" not in ind.s
1684 ):
1685 # Remove the indentation of the first item if not newline
1686 self._value[idx].indent = None
1687 comma_in_indent = (
1688 group_rm.indent is not None and "," in group_rm.indent.s
1689 )
1690 comma_in_comma = group_rm.comma is not None and "," in group_rm.comma.s
1691 if comma_in_indent and comma_in_comma:
1692 # Removed group had both commas. Add one to the next group.
1693 group = self._value[idx] if len(self._value) > idx else None
1694 if group is not None:
1695 if group.indent is None:
1696 group.indent = Whitespace(",")
1697 elif "," not in group.indent.s:
1698 # Insert the comma after the newline
1699 try:
1700 newline_index = group.indent.s.index("\n")
1701 group.indent._s = (
1702 group.indent.s[: newline_index + 1]
1703 + ","
1704 + group.indent.s[newline_index + 1 :]
1705 )
1706 except ValueError:
1707 group.indent._s = "," + group.indent.s
1708 elif not comma_in_indent and not comma_in_comma:
1709 # Removed group had no commas. Remove the next comma found.
1710 for j in range(idx, len(self._value)):
1711 group = self._value[j]
1712 if group.indent is not None and "," in group.indent.s:
1713 group.indent._s = group.indent.s.replace(",", "", 1)
1714 break
1715 if group_rm.indent is not None and "\n" in group_rm.indent.s:
1716 # Restore the removed group's newline onto the next group
1717 # if the next group does not have a newline.
1718 # i.e. the two were on the same line
1719 group = self._value[idx] if len(self._value) > idx else None
1720 if group is not None and (
1721 group.indent is None or "\n" not in group.indent.s
1722 ):
1723 group.indent = group_rm.indent
1725 if len(self._value) > 0:
1726 v = self._value[-1]
1727 if not v.is_whitespace():
1728 # remove the comma of the last item
1729 v.comma = None
1731 self._reindex()
1733 def _getstate(self, protocol: int = 3) -> tuple[list[Item], Trivia, bool]:
1734 return list(self._iter_items()), self._trivia, self._multiline
1737class AbstractTable(Item, _CustomDict): # type: ignore[type-arg]
1738 """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
1740 def __init__(self, value: container.Container, trivia: Trivia):
1741 Item.__init__(self, trivia)
1743 self._value = value
1745 for k, v in self._value.body:
1746 if k is not None:
1747 dict.__setitem__(self, k.key, v)
1749 def unwrap(self) -> dict[str, Any]:
1750 unwrapped = {}
1751 for k, v in self.items():
1752 if isinstance(k, Key):
1753 k = k.key
1754 if hasattr(v, "unwrap"):
1755 v = v.unwrap()
1756 unwrapped[k] = v
1758 return unwrapped
1760 @property
1761 def value(self) -> container.Container:
1762 return self._value
1764 @overload
1765 def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ...
1767 @overload
1768 def append(self: AT, key: Key | str, value: Any) -> AT: ...
1770 def append(self: AT, key: Key | str | None, value: Any) -> AT:
1771 raise NotImplementedError
1773 @overload
1774 def add(self: AT, key: Comment | Whitespace) -> AT: ...
1776 @overload
1777 def add(self: AT, key: Key | str, value: Any = ...) -> AT: ...
1779 def add(
1780 self: AT, key: Key | str | Comment | Whitespace, value: Any | None = None
1781 ) -> AT:
1782 if value is None:
1783 if not isinstance(key, (Comment, Whitespace)):
1784 msg = "Non comment/whitespace items must have an associated key"
1785 raise ValueError(msg)
1787 return self.append(None, key)
1789 if isinstance(key, (Comment, Whitespace)):
1790 raise ValueError("Comment/Whitespace keys must not have a value")
1792 return self.append(key, value)
1794 def remove(self: AT, key: Key | str) -> AT:
1795 self._value.remove(key)
1797 if isinstance(key, Key):
1798 key = key.key
1800 if key is not None:
1801 dict.__delitem__(self, key)
1803 return self
1805 def item(self, key: Key | str) -> Item | OutOfOrderTableProxy:
1806 return self._value.item(key)
1808 def setdefault(self, key: Key | str, default: Any) -> Any: # type: ignore[override]
1809 super().setdefault(key, default)
1810 return self[key]
1812 def __str__(self) -> str:
1813 return str(self.value)
1815 def copy(self: AT) -> AT:
1816 return copy.copy(self)
1818 def __repr__(self) -> str:
1819 return repr(self.value)
1821 def __iter__(self) -> Iterator[str]:
1822 return iter(self._value)
1824 def __len__(self) -> int:
1825 return len(self._value)
1827 def __delitem__(self, key: Key | str) -> None: # type: ignore[override]
1828 self.remove(key)
1830 def __getitem__(self, key: Key | str) -> Any: # type: ignore[override]
1831 return self._value[key]
1833 def __contains__(self, key: object) -> bool:
1834 # Native membership test. The inherited ``MutableMapping.__contains__``
1835 # resolves the value via ``__getitem__`` (``self._value[key]``) -- and
1836 # builds a ``NonExistentKey`` on every absent key -- only to discard it.
1837 # Delegate straight to the inner container instead: its ``__contains__``
1838 # answers from ``_map`` while still building the ``OutOfOrderTableProxy``
1839 # for an out-of-order entry, so validation runs exactly as before.
1840 return key in self._value
1842 def __setitem__(self, key: Key | str, value: Any) -> None: # type: ignore[override]
1843 if not isinstance(value, Item):
1844 value = item(value, _parent=self)
1846 is_replace = key in self
1847 self._value[key] = value
1849 if key is not None:
1850 dict.__setitem__(self, key, value)
1852 if is_replace:
1853 return
1854 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1855 if not m:
1856 return
1858 indent = m.group(1)
1860 if not isinstance(value, Whitespace):
1861 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
1862 if not m:
1863 value.trivia.indent = indent
1864 else:
1865 value.trivia.indent = m.group(1) + indent + m.group(2)
1868class Table(AbstractTable):
1869 """
1870 A table literal.
1871 """
1873 def __init__(
1874 self,
1875 value: container.Container,
1876 trivia: Trivia,
1877 is_aot_element: bool,
1878 is_super_table: bool | None = None,
1879 name: str | None = None,
1880 display_name: str | None = None,
1881 ) -> None:
1882 super().__init__(value, trivia)
1884 self.name = name
1885 self.display_name = display_name
1886 self._is_aot_element = is_aot_element
1887 self._is_super_table = is_super_table
1889 @property
1890 def discriminant(self) -> int:
1891 return 9
1893 def __copy__(self) -> Table:
1894 return type(self)(
1895 self._value.copy(),
1896 self._trivia.copy(),
1897 self._is_aot_element,
1898 self._is_super_table,
1899 self.name,
1900 self.display_name,
1901 )
1903 def append(self, key: Key | str | None, _item: Any) -> Table:
1904 """
1905 Appends a (key, item) to the table.
1906 """
1907 if not isinstance(_item, Item):
1908 _item = item(_item, _parent=self)
1910 self._value.append(key, _item)
1912 if isinstance(key, Key):
1913 key = next(iter(key)).key
1914 _item = self._value[key]
1916 if key is not None:
1917 dict.__setitem__(self, key, _item)
1919 m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1920 if not m:
1921 return self
1923 indent = m.group(1)
1925 if not isinstance(_item, Whitespace):
1926 m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
1927 if not m:
1928 _item.trivia.indent = indent
1929 else:
1930 _item.trivia.indent = m.group(1) + indent + m.group(2)
1932 return self
1934 def raw_append(self, key: Key | str | None, _item: Any) -> Table:
1935 """Similar to :meth:`append` but does not copy indentation."""
1936 if not isinstance(_item, Item):
1937 _item = item(_item)
1939 self._value.append(key, _item, validate=False)
1941 if isinstance(key, Key):
1942 key = next(iter(key)).key
1943 _item = self._value[key]
1945 if key is not None:
1946 dict.__setitem__(self, key, _item)
1948 return self
1950 def is_aot_element(self) -> bool:
1951 """True if the table is the direct child of an AOT element."""
1952 return self._is_aot_element
1954 def is_super_table(self) -> bool:
1955 """A super table is the intermediate parent of a nested table as in [a.b.c].
1956 If true, it won't appear in the TOML representation."""
1957 if self._is_super_table is not None:
1958 return self._is_super_table
1959 if not self:
1960 return False
1961 # If the table has children and all children are tables, then it is a super table.
1962 for k, child in self.items():
1963 if not isinstance(k, Key):
1964 k = SingleKey(k)
1965 index = self.value._map[k]
1966 if isinstance(index, tuple):
1967 return False
1968 real_key = self.value.body[index][0]
1969 if (
1970 not isinstance(child, (Table, AoT))
1971 or real_key is None
1972 or real_key.is_dotted()
1973 ):
1974 return False
1975 return True
1977 def as_string(self) -> str:
1978 return self._value.as_string()
1980 # Helpers
1982 def indent(self, indent: int) -> Table:
1983 """Indent the table with given number of spaces."""
1984 super().indent(indent)
1986 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
1987 if not m:
1988 indent_str = ""
1989 else:
1990 indent_str = m.group(1)
1992 for _, item in self._value.body:
1993 if not isinstance(item, Whitespace):
1994 item.trivia.indent = indent_str + item.trivia.indent
1996 return self
1998 def invalidate_display_name(self) -> None:
1999 """Call ``invalidate_display_name`` on the contained tables"""
2000 self.display_name = None
2002 for child in self.values():
2003 if hasattr(child, "invalidate_display_name"):
2004 child.invalidate_display_name()
2006 def _getstate(
2007 self, protocol: int = 3
2008 ) -> tuple[container.Container, Trivia, bool, bool | None, str | None, str | None]:
2009 return (
2010 self._value,
2011 self._trivia,
2012 self._is_aot_element,
2013 self._is_super_table,
2014 self.name,
2015 self.display_name,
2016 )
2019class InlineTable(AbstractTable):
2020 """
2021 An inline table literal.
2022 """
2024 def __init__(
2025 self, value: container.Container, trivia: Trivia, new: bool = False
2026 ) -> None:
2027 super().__init__(value, trivia)
2029 self._new = new
2031 @property
2032 def discriminant(self) -> int:
2033 return 10
2035 def append(self, key: Key | str | None, _item: Any) -> InlineTable:
2036 """
2037 Appends a (key, item) to the table.
2038 """
2039 if not isinstance(_item, Item):
2040 _item = item(_item, _parent=self)
2042 if not isinstance(_item, (Whitespace, Comment)):
2043 if not _item.trivia.indent and len(self._value) > 0 and not self._new:
2044 _item.trivia.indent = " "
2045 if _item.trivia.comment:
2046 _item.trivia.comment = ""
2048 self._value.append(key, _item)
2050 if isinstance(key, Key):
2051 key = key.key
2053 if key is not None:
2054 dict.__setitem__(self, key, _item)
2056 return self
2058 def as_string(self) -> str:
2059 buf = "{"
2060 emitted_key = False
2061 needs_separator = False
2062 has_explicit_commas = any(
2063 k is None and isinstance(v, Whitespace) and "," in v.s
2064 for k, v in self._value.body
2065 )
2066 last_item_idx = next(
2067 (
2068 i
2069 for i in range(len(self._value.body) - 1, -1, -1)
2070 if self._value.body[i][0] is not None
2071 ),
2072 None,
2073 )
2074 pending_separator = False
2075 for i, (k, v) in enumerate(self._value.body):
2076 if k is None:
2077 if isinstance(v, Whitespace) and "," in v.s:
2078 if not emitted_key:
2079 buf += v.as_string().replace(",", "", 1)
2080 continue
2082 has_following_null = any(
2083 isinstance(next_v, Null)
2084 for _, next_v in self._value.body[i + 1 :]
2085 )
2086 has_following_key = any(
2087 next_k is not None for next_k, _ in self._value.body[i + 1 :]
2088 )
2089 if has_following_null and not has_following_key:
2090 buf += v.as_string().replace(",", "", 1)
2091 continue
2093 if pending_separator:
2094 # A previous key was removed, leaving this comma as a
2095 # duplicate of an already emitted separator. Drop it to
2096 # avoid producing an invalid ``, ,`` sequence.
2097 buf += v.as_string().replace(",", "", 1)
2098 continue
2100 pending_separator = True
2102 if i == len(self._value.body) - 1:
2103 if self._new:
2104 buf = buf.rstrip(", ")
2105 elif not has_explicit_commas or "," in v.as_string():
2106 buf = buf.rstrip(",")
2108 v_string = v.as_string()
2109 buf += v_string
2110 if "," in v_string:
2111 needs_separator = False
2113 continue
2115 if has_explicit_commas and needs_separator:
2116 stripped = buf.rstrip()
2117 buf = f"{stripped},{buf[len(stripped) :]}"
2118 needs_separator = False
2120 v_trivia_trail = v.trivia.trail.replace("\n", "")
2121 if k.is_dotted() and isinstance(v, Table):
2122 # A table materialized from a dotted key renders one
2123 # `prefix.child = value` pair per child, so that keys added
2124 # after parsing stay attached to the dotted prefix.
2125 rendered = ", ".join(self._render_dotted(k, v))
2126 else:
2127 rendered = (
2128 f"{k.as_string() + ('.' if k.is_dotted() else '')}"
2129 f"{k.sep}"
2130 f"{v.as_string()}"
2131 )
2132 buf += f"{v.trivia.indent}{rendered}{v.trivia.comment}{v_trivia_trail}"
2133 emitted_key = True
2134 pending_separator = False
2135 if has_explicit_commas:
2136 needs_separator = True
2138 if (
2139 not has_explicit_commas
2140 and last_item_idx is not None
2141 and i < last_item_idx
2142 ):
2143 buf += ","
2144 if self._new:
2145 buf += " "
2147 buf += "}"
2149 return buf
2151 def _render_dotted(self, key: Key, table: Table) -> list[str]:
2152 """Render a table materialized from a dotted key as a list of
2153 ``prefix.child = value`` strings, recursing into nested dotted
2154 children."""
2155 prefix = f"{key.as_string()}.{key.sep}"
2156 parts = []
2157 for k, v in table.value.body:
2158 if k is None:
2159 continue
2160 if isinstance(v, Table):
2161 parts.extend(f"{prefix}{sub}" for sub in self._render_dotted(k, v))
2162 else:
2163 trail = v.trivia.trail.replace("\n", "")
2164 parts.append(
2165 f"{prefix}{v.trivia.indent}{k.as_string()}{k.sep}{v.as_string()}"
2166 f"{v.trivia.comment_ws}{v.trivia.comment}{trail}"
2167 )
2168 return parts
2170 def __setitem__(self, key: Key | str, value: Any) -> None: # type: ignore[override]
2171 if hasattr(value, "trivia") and value.trivia.comment:
2172 value.trivia.comment = ""
2173 super().__setitem__(key, value)
2175 def __copy__(self) -> InlineTable:
2176 return type(self)(self._value.copy(), self._trivia.copy(), self._new)
2178 def _getstate(self, protocol: int = 3) -> tuple[container.Container, Trivia]:
2179 return (self._value, self._trivia)
2182class String(str, Item): # type: ignore[misc]
2183 """
2184 A string literal.
2185 """
2187 def __new__(
2188 cls, t: StringType, value: str, original: str, trivia: Trivia
2189 ) -> String:
2190 return super().__new__(cls, value)
2192 def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
2193 super().__init__(trivia)
2195 self._t = t
2196 self._original = original
2198 def unwrap(self) -> str:
2199 return str(self)
2201 @property
2202 def discriminant(self) -> int:
2203 return 11
2205 @property
2206 def value(self) -> str:
2207 return self
2209 def as_string(self) -> str:
2210 return f"{self._t.value}{decode(self._original)}{self._t.value}"
2212 @property
2213 def type(self) -> StringType:
2214 return self._t
2216 def __add__(self, other: str) -> String:
2217 result = super().__add__(other)
2218 original = self._original + getattr(other, "_original", other)
2220 return self._new(result, original)
2222 def _new(self, result: str, original: str) -> String:
2223 return String(self._t, result, original, self._trivia)
2225 def _getstate(self, protocol: int = 3) -> tuple[StringType, str, str, Trivia]:
2226 return self._t, str(self), self._original, self._trivia
2228 @classmethod
2229 def from_raw(
2230 cls, value: str, type_: StringType = StringType.SLB, escape: bool = True
2231 ) -> String:
2232 value = decode(value)
2234 invalid = type_.invalid_sequences
2235 if any(c in value for c in invalid):
2236 raise InvalidStringError(value, invalid, type_.value)
2238 escaped = type_.escaped_sequences
2239 string_value = escape_string(value, escaped) if escape and escaped else value
2241 return cls(type_, decode(value), string_value, Trivia())
2244class AoT(Item, _CustomList): # type: ignore[type-arg]
2245 """
2246 An array of table literal
2247 """
2249 def __init__(
2250 self, body: list[Table], name: str | None = None, parsed: bool = False
2251 ) -> None:
2252 self.name = name
2253 self._body: list[Table] = []
2254 self._parsed = parsed
2256 super().__init__(Trivia(trail=""))
2258 for table in body:
2259 self.append(table)
2261 def unwrap(self) -> list[dict[str, Any]]:
2262 unwrapped = []
2263 for t in self._body:
2264 if hasattr(t, "unwrap"):
2265 unwrapped.append(t.unwrap())
2266 else:
2267 unwrapped.append(t)
2268 return unwrapped
2270 @property
2271 def body(self) -> list[Table]:
2272 return self._body
2274 @property
2275 def discriminant(self) -> int:
2276 return 12
2278 @property
2279 def value(self) -> list[dict[str, Any]]:
2280 return [v.value for v in self._body]
2282 def __len__(self) -> int:
2283 return len(self._body)
2285 @overload # type: ignore[override]
2286 def __getitem__(self, key: slice) -> list[Table]: ...
2288 @overload
2289 def __getitem__(self, key: int) -> Table: ...
2291 def __getitem__(self, key: int | slice) -> Table | list[Table]:
2292 return self._body[key]
2294 def __setitem__(self, key: slice | int, value: Any) -> None: # type: ignore[override]
2295 self._body[key] = item(value, _parent=self)
2297 def __delitem__(self, key: slice | int) -> None: # type: ignore[override]
2298 del self._body[key]
2299 list.__delitem__(self, key)
2301 def insert(self, index: int, value: dict[str, Any]) -> None: # type: ignore[override]
2302 value = item(value, _parent=self)
2303 if not isinstance(value, Table):
2304 raise ValueError(f"Unsupported insert value type: {type(value)}")
2305 length = len(self)
2306 if index < 0:
2307 index += length
2308 if index < 0:
2309 index = 0
2310 elif index >= length:
2311 index = length
2312 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
2313 if m:
2314 indent = m.group(1)
2316 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
2317 if not m:
2318 value.trivia.indent = indent
2319 else:
2320 value.trivia.indent = m.group(1) + indent + m.group(2)
2321 prev_table = self._body[index - 1] if 0 < index and length else None
2322 next_table = self._body[index + 1] if index < length - 1 else None
2323 if not self._parsed:
2324 if prev_table and "\n" not in value.trivia.indent:
2325 value.trivia.indent = "\n" + value.trivia.indent
2326 if next_table and "\n" not in next_table.trivia.indent:
2327 next_table.trivia.indent = "\n" + next_table.trivia.indent
2328 self._body.insert(index, value)
2329 list.insert(self, index, value)
2331 def invalidate_display_name(self) -> None:
2332 """Call ``invalidate_display_name`` on the contained tables"""
2333 for child in self:
2334 if hasattr(child, "invalidate_display_name"):
2335 child.invalidate_display_name()
2337 def as_string(self) -> str:
2338 b = ""
2339 for table in self._body:
2340 b += table.as_string()
2342 return b
2344 def __repr__(self) -> str:
2345 return f"<AoT {self.value}>"
2347 def _getstate(self, protocol: int = 3) -> tuple[list[Table], str | None, bool]:
2348 return self._body, self.name, self._parsed
2351class Null(Item):
2352 """
2353 A null item.
2354 """
2356 def __init__(self) -> None:
2357 super().__init__(Trivia(trail=""))
2359 def unwrap(self) -> None:
2360 return None
2362 @property
2363 def discriminant(self) -> int:
2364 return -1
2366 @property
2367 def value(self) -> None:
2368 return None
2370 def as_string(self) -> str:
2371 return ""
2373 def _getstate(self, protocol: int = 3) -> tuple[()]:
2374 return ()