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

1382 statements  

1from __future__ import annotations 

2 

3import abc 

4import copy 

5import dataclasses 

6import inspect 

7import re 

8import string 

9 

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 

24 

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 

35 

36 

37if TYPE_CHECKING: 

38 from typing import Protocol 

39 

40 from tomlkit import container 

41 from tomlkit.container import OutOfOrderTableProxy 

42 

43 class Encoder(Protocol): 

44 def __call__(self, __value: Any, /) -> Item: ... 

45 

46 

47ItemT = TypeVar("ItemT", bound="Item") 

48CUSTOM_ENCODERS: list[Encoder] = [] 

49AT = TypeVar("AT", bound="AbstractTable") 

50 

51 

52@overload 

53def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ... # type: ignore[overload-overlap] 

54 

55 

56@overload 

57def item(value: int, _parent: Item | None = ..., _sort_keys: bool = ...) -> Integer: ... 

58 

59 

60@overload 

61def item(value: float, _parent: Item | None = ..., _sort_keys: bool = ...) -> Float: ... 

62 

63 

64@overload 

65def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> String: ... 

66 

67 

68@overload 

69def item( # type: ignore[overload-overlap] 

70 value: datetime, _parent: Item | None = ..., _sort_keys: bool = ... 

71) -> DateTime: ... 

72 

73 

74@overload 

75def item(value: date, _parent: Item | None = ..., _sort_keys: bool = ...) -> Date: ... 

76 

77 

78@overload 

79def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Time: ... 

80 

81 

82@overload 

83def item( 

84 value: Sequence[dict[str, Any]], _parent: Item | None = ..., _sort_keys: bool = ... 

85) -> AoT: ... 

86 

87 

88@overload 

89def item( 

90 value: Sequence[Any], _parent: Item | None = ..., _sort_keys: bool = ... 

91) -> Array: ... 

92 

93 

94@overload 

95def item( 

96 value: dict[str, Any], _parent: Array = ..., _sort_keys: bool = ... 

97) -> InlineTable: ... 

98 

99 

100@overload 

101def item( 

102 value: dict[str, Any], _parent: Item | None = ..., _sort_keys: bool = ... 

103) -> Table: ... 

104 

105 

106@overload 

107def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ... 

108 

109 

110@overload 

111def item(value: object, _parent: Item | None = ..., _sort_keys: bool = ...) -> Item: ... 

112 

113 

114def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item: 

115 """Create a TOML item from a Python object. 

116 

117 :Example: 

118 

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 """ 

127 

128 from tomlkit.container import Container 

129 

130 if isinstance(value, Item): 

131 return value 

132 

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) 

149 

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 

163 

164 if table_constructor is Table: 

165 # AoT tables render as ``[[table]]`` headers, so dict-valued keys 

166 # must come last to avoid them capturing the following keys. 

167 def _sort_key(i: tuple[Any, Any]) -> Any: 

168 return (isinstance(i[1], dict), i[0] if _sort_keys else 1) 

169 else: 

170 # Inline tables cannot capture, so there is no need to put 

171 # dict-valued keys last: preserve insertion order, or sort purely 

172 # by key when explicitly requested. See #546. 

173 def _sort_key(i: tuple[Any, Any]) -> Any: 

174 return i[0] if _sort_keys else 1 

175 

176 for v in value: 

177 if isinstance(v, dict): 

178 table = table_constructor(Container(), Trivia(), True) 

179 

180 for k, _v in sorted(v.items(), key=_sort_key): 

181 i = item(_v, _parent=table, _sort_keys=_sort_keys) 

182 if isinstance(table, InlineTable): 

183 i.trivia.trail = "" 

184 

185 table[k] = i 

186 

187 v = table 

188 

189 a.append(v) 

190 

191 return a 

192 elif isinstance(value, str): 

193 return String.from_raw(value) 

194 elif isinstance(value, datetime): 

195 return DateTime( 

196 value.year, 

197 value.month, 

198 value.day, 

199 value.hour, 

200 value.minute, 

201 value.second, 

202 value.microsecond, 

203 value.tzinfo, 

204 Trivia(), 

205 value.isoformat().replace("+00:00", "Z"), 

206 fold=value.fold, 

207 ) 

208 elif isinstance(value, date): 

209 return Date(value.year, value.month, value.day, Trivia(), value.isoformat()) 

210 elif isinstance(value, time): 

211 return Time( 

212 value.hour, 

213 value.minute, 

214 value.second, 

215 value.microsecond, 

216 value.tzinfo, 

217 Trivia(), 

218 _format_time(value), 

219 fold=value.fold, 

220 ) 

221 else: 

222 for encoder in CUSTOM_ENCODERS: 

223 try: 

224 # Check if encoder accepts keyword arguments for backward compatibility 

225 sig = inspect.signature(encoder) 

226 if "_parent" in sig.parameters or any( 

227 p.kind == p.VAR_KEYWORD for p in sig.parameters.values() 

228 ): 

229 # New style encoder that can accept additional parameters 

230 rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys) # type: ignore[call-arg] 

231 else: 

232 # Old style encoder that only accepts value 

233 rv = encoder(value) 

234 except ConvertError: 

235 pass 

236 else: 

237 if not isinstance(rv, Item): 

238 raise ConvertError( 

239 f"Custom encoder is expected to return an instance of Item, got {type(rv)}" 

240 ) 

241 return rv 

242 

243 raise ConvertError(f"Unable to convert an object of {type(value)} to a TOML item") 

244 

245 

246def _format_time(value: time) -> str: 

247 # TOML local times carry no offset or timezone (the spec's examples 

248 # are plain ``07:32:00``); keeping the offset in the rendered raw 

249 # value would produce a literal this library's own parser rejects. 

250 # Built from the fields (not ``isoformat``/``replace``) so it stays 

251 # correct even when called on a ``Time`` from ``Time._new``. 

252 s = f"{value.hour:02d}:{value.minute:02d}:{value.second:02d}" 

253 if value.microsecond: 

254 s += f".{value.microsecond:06d}".rstrip("0") 

255 return s 

256 

257 

258# A carriage return that is not the CR of a CRLF line ending. Inside a multiline 

259# string only a raw line feed or a CRLF pair is a valid line ending; a lone CR is 

260# a control character that a basic string must escape. 

261_BARE_CR = re.compile(r"\r(?!\n)") 

262 

263 

264class StringType(Enum): 

265 # Single Line Basic 

266 SLB = '"' 

267 # Multi Line Basic 

268 MLB = '"""' 

269 # Single Line Literal 

270 SLL = "'" 

271 # Multi Line Literal 

272 MLL = "'''" 

273 

274 @classmethod 

275 def select(cls, literal: bool = False, multiline: bool = False) -> StringType: 

276 return { 

277 (False, False): cls.SLB, 

278 (False, True): cls.MLB, 

279 (True, False): cls.SLL, 

280 (True, True): cls.MLL, 

281 }[(literal, multiline)] 

282 

283 @property 

284 def escaped_sequences(self) -> Collection[str]: 

285 # https://toml.io/en/v1.0.0#string 

286 escaped_in_basic = CONTROL_CHARS | {"\\"} 

287 allowed_in_multiline = {"\n", "\r"} 

288 return { 

289 StringType.SLB: escaped_in_basic | {'"'}, 

290 StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline, 

291 StringType.SLL: (), 

292 StringType.MLL: (), 

293 }[self] 

294 

295 @property 

296 def invalid_sequences(self) -> Collection[str]: 

297 # https://toml.io/en/v1.0.0#string 

298 forbidden_in_literal = CONTROL_CHARS - {"\t"} 

299 # CR stays forbidden in multiline: the value is checked with CRLF 

300 # pairs collapsed, so any remaining CR is a lone control character 

301 return { 

302 StringType.SLB: (), 

303 StringType.MLB: (), 

304 StringType.SLL: forbidden_in_literal | {"'"}, 

305 StringType.MLL: (forbidden_in_literal | {"'''"}) - {"\n"}, 

306 }[self] 

307 

308 @property 

309 def unit(self) -> str: 

310 return self.value[0] 

311 

312 def is_basic(self) -> bool: 

313 return self is StringType.SLB or self is StringType.MLB 

314 

315 def is_literal(self) -> bool: 

316 return self is StringType.SLL or self is StringType.MLL 

317 

318 def is_singleline(self) -> bool: 

319 return self is StringType.SLB or self is StringType.SLL 

320 

321 def is_multiline(self) -> bool: 

322 return self is StringType.MLB or self is StringType.MLL 

323 

324 def toggle(self) -> StringType: 

325 return { 

326 StringType.SLB: StringType.MLB, 

327 StringType.MLB: StringType.SLB, 

328 StringType.SLL: StringType.MLL, 

329 StringType.MLL: StringType.SLL, 

330 }[self] 

331 

332 

333class BoolType(Enum): 

334 TRUE = "true" 

335 FALSE = "false" 

336 

337 def __bool__(self) -> bool: 

338 return {BoolType.TRUE: True, BoolType.FALSE: False}[self] 

339 

340 def __iter__(self) -> Iterator[str]: 

341 return iter(self.value) 

342 

343 def __len__(self) -> int: 

344 return len(self.value) 

345 

346 

347@dataclasses.dataclass 

348class Trivia: 

349 """ 

350 Trivia information (aka metadata). 

351 """ 

352 

353 # Whitespace before a value. 

354 indent: str = "" 

355 # Whitespace after a value, but before a comment. 

356 comment_ws: str = "" 

357 # Comment, starting with # character, or empty string if no comment. 

358 comment: str = "" 

359 # Trailing newline. 

360 trail: str = "\n" 

361 

362 def copy(self) -> Trivia: 

363 return dataclasses.replace(self) 

364 

365 

366class KeyType(Enum): 

367 """ 

368 The type of a Key. 

369 

370 Keys can be bare (unquoted), or quoted using basic ("), or literal (') 

371 quotes following the same escaping rules as single-line StringType. 

372 """ 

373 

374 Bare = "" 

375 Basic = '"' 

376 Literal = "'" 

377 

378 

379class Key(abc.ABC): 

380 """Base class for a key""" 

381 

382 sep: str 

383 _original: str 

384 _keys: list[SingleKey] 

385 _dotted: bool 

386 key: str 

387 

388 @abc.abstractmethod 

389 def __hash__(self) -> int: 

390 pass 

391 

392 @abc.abstractmethod 

393 def __eq__(self, __o: object) -> bool: 

394 pass 

395 

396 def is_dotted(self) -> bool: 

397 """If the key is followed by other keys""" 

398 return self._dotted 

399 

400 def __iter__(self) -> Iterator[SingleKey]: 

401 return iter(self._keys) 

402 

403 def concat(self, other: Key) -> DottedKey: 

404 """Concatenate keys into a dotted key""" 

405 keys = self._keys + other._keys 

406 return DottedKey(keys, sep=self.sep) 

407 

408 def is_multi(self) -> bool: 

409 """Check if the key contains multiple keys""" 

410 return len(self._keys) > 1 

411 

412 def as_string(self) -> str: 

413 """The TOML representation""" 

414 return self._original 

415 

416 def __str__(self) -> str: 

417 return self.as_string() 

418 

419 def __repr__(self) -> str: 

420 return f"<Key {self.as_string()}>" 

421 

422 

423class SingleKey(Key): 

424 """A single key""" 

425 

426 def __init__( 

427 self, 

428 k: str, 

429 t: KeyType | None = None, 

430 sep: str | None = None, 

431 original: str | None = None, 

432 ) -> None: 

433 if not isinstance(k, str): 

434 raise TypeError("Keys must be strings") 

435 

436 if t is None: 

437 if not k or any( 

438 c not in string.ascii_letters + string.digits + "-" + "_" for c in k 

439 ): 

440 t = KeyType.Basic 

441 else: 

442 t = KeyType.Bare 

443 

444 self.t = t 

445 if sep is None: 

446 sep = " = " 

447 

448 self.sep = sep 

449 self.key = k 

450 if original is None: 

451 key_str = escape_string(k) if t == KeyType.Basic else k 

452 original = f"{t.value}{key_str}{t.value}" 

453 

454 self._original = original 

455 self._keys = [self] 

456 self._dotted = False 

457 

458 @property 

459 def delimiter(self) -> str: 

460 """The delimiter: double quote/single quote/none""" 

461 return self.t.value 

462 

463 def is_bare(self) -> bool: 

464 """Check if the key is bare""" 

465 return self.t == KeyType.Bare 

466 

467 def __hash__(self) -> int: 

468 return hash(self.key) 

469 

470 def __eq__(self, other: Any) -> bool: 

471 if isinstance(other, Key): 

472 return isinstance(other, SingleKey) and self.key == other.key 

473 

474 return bool(self.key == other) 

475 

476 

477class DottedKey(Key): 

478 def __init__( 

479 self, 

480 keys: Iterable[SingleKey], 

481 sep: str | None = None, 

482 original: str | None = None, 

483 ) -> None: 

484 self._keys = list(keys) 

485 if original is None: 

486 original = ".".join(k.as_string() for k in self._keys) 

487 

488 self.sep = " = " if sep is None else sep 

489 self._original = original 

490 self._dotted = False 

491 self.key = ".".join(k.key for k in self._keys) 

492 

493 def __hash__(self) -> int: 

494 return hash(tuple(self._keys)) 

495 

496 def __eq__(self, __o: object) -> bool: 

497 return isinstance(__o, DottedKey) and self._keys == __o._keys 

498 

499 

500class Item: 

501 """ 

502 An item within a TOML document. 

503 """ 

504 

505 def __init__(self, trivia: Trivia) -> None: 

506 self._trivia = trivia 

507 

508 @property 

509 def trivia(self) -> Trivia: 

510 """The trivia element associated with this item""" 

511 return self._trivia 

512 

513 @property 

514 def discriminant(self) -> int: 

515 raise NotImplementedError() 

516 

517 def as_string(self) -> str: 

518 """The TOML representation""" 

519 raise NotImplementedError() 

520 

521 @property 

522 def value(self) -> Any: 

523 return self 

524 

525 def unwrap(self) -> Any: 

526 """Returns as pure python object (ppo)""" 

527 raise NotImplementedError() 

528 

529 # Helpers 

530 

531 def comment(self, comment: str) -> Item: 

532 """Attach a comment to this item""" 

533 if "\n" in comment or "\r" in comment: 

534 raise ValueError("Comment cannot contain line breaks") 

535 if not comment.strip().startswith("#"): 

536 comment = "# " + comment 

537 

538 self._trivia.comment_ws = " " 

539 self._trivia.comment = comment 

540 

541 return self 

542 

543 def indent(self, indent: int) -> Item: 

544 """Indent this item with given number of spaces""" 

545 if self._trivia.indent.startswith("\n"): 

546 self._trivia.indent = "\n" + " " * indent 

547 else: 

548 self._trivia.indent = " " * indent 

549 

550 return self 

551 

552 def is_boolean(self) -> bool: 

553 return isinstance(self, Bool) 

554 

555 def is_table(self) -> bool: 

556 return isinstance(self, Table) 

557 

558 def is_inline_table(self) -> bool: 

559 return isinstance(self, InlineTable) 

560 

561 def is_aot(self) -> bool: 

562 return isinstance(self, AoT) 

563 

564 def _getstate(self, protocol: int = 3) -> tuple[object, ...]: 

565 return (self._trivia,) 

566 

567 def __reduce__(self) -> tuple[type, tuple[object, ...]]: 

568 return self.__reduce_ex__(2) 

569 

570 def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[object, ...]]: # type: ignore[override] 

571 return self.__class__, self._getstate(protocol) 

572 

573 

574class Whitespace(Item): 

575 """ 

576 A whitespace literal. 

577 """ 

578 

579 def __init__(self, s: str, fixed: bool = False) -> None: 

580 self._s = s 

581 self._fixed = fixed 

582 

583 @property 

584 def s(self) -> str: 

585 return self._s 

586 

587 @property 

588 def value(self) -> str: 

589 """The wrapped string of the whitespace""" 

590 return self._s 

591 

592 @property 

593 def trivia(self) -> Trivia: 

594 raise RuntimeError("Called trivia on a Whitespace variant.") 

595 

596 @property 

597 def discriminant(self) -> int: 

598 return 0 

599 

600 def is_fixed(self) -> bool: 

601 """If the whitespace is fixed, it can't be merged or discarded from the output.""" 

602 return self._fixed 

603 

604 def as_string(self) -> str: 

605 return self._s 

606 

607 def __repr__(self) -> str: 

608 return f"<{self.__class__.__name__} {self._s!r}>" 

609 

610 def _getstate(self, protocol: int = 3) -> tuple[str, bool]: 

611 return self._s, self._fixed 

612 

613 

614class Comment(Item): 

615 """ 

616 A comment literal. 

617 """ 

618 

619 @property 

620 def discriminant(self) -> int: 

621 return 1 

622 

623 def as_string(self) -> str: 

624 return ( 

625 f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}" 

626 ) 

627 

628 def __str__(self) -> str: 

629 return f"{self._trivia.indent}{decode(self._trivia.comment)}" 

630 

631 

632class Integer(Item, _CustomInt): 

633 """ 

634 An integer literal. 

635 """ 

636 

637 def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer: 

638 return int.__new__(cls, value) 

639 

640 def __init__(self, value: int, trivia: Trivia, raw: str) -> None: 

641 super().__init__(trivia) 

642 self._original = value 

643 self._raw = raw 

644 self._sign = False 

645 

646 if re.match(r"^[+\-]\d+$", raw): 

647 self._sign = True 

648 

649 def unwrap(self) -> int: 

650 return self._original 

651 

652 __int__ = unwrap 

653 

654 def __hash__(self) -> int: 

655 return hash(self.unwrap()) 

656 

657 @property 

658 def discriminant(self) -> int: 

659 return 2 

660 

661 @property 

662 def value(self) -> int: 

663 """The wrapped integer value""" 

664 return self 

665 

666 def as_string(self) -> str: 

667 return self._raw 

668 

669 def _new(self, result: int) -> Integer: 

670 raw = str(result) 

671 if self._sign and result >= 0: 

672 raw = f"+{raw}" 

673 

674 return Integer(result, self._trivia.copy(), raw) 

675 

676 def _getstate(self, protocol: int = 3) -> tuple[int, Trivia, str]: 

677 return int(self), self._trivia, self._raw 

678 

679 # int methods — explicit typed wrappers 

680 def __abs__(self) -> Integer: 

681 return self._new(int.__abs__(self)) 

682 

683 def __add__(self, other: object) -> Integer: 

684 result = int.__add__(self, other) # type: ignore[operator] 

685 if result is NotImplemented: 

686 return result # type: ignore[return-value] 

687 return self._new(result) 

688 

689 def __and__(self, other: object) -> Integer: 

690 result = int.__and__(self, other) # type: ignore[operator] 

691 if result is NotImplemented: 

692 return result # type: ignore[return-value] 

693 return self._new(result) 

694 

695 def __ceil__(self) -> Integer: 

696 return self._new(int.__ceil__(self)) 

697 

698 __eq__ = int.__eq__ 

699 

700 def __floor__(self) -> Integer: 

701 return self._new(int.__floor__(self)) 

702 

703 def __floordiv__(self, other: object) -> Integer: 

704 result = int.__floordiv__(self, other) # type: ignore[operator] 

705 if result is NotImplemented: 

706 return result # type: ignore[return-value] 

707 return self._new(result) 

708 

709 def __invert__(self) -> Integer: 

710 return self._new(int.__invert__(self)) 

711 

712 __le__ = int.__le__ 

713 

714 def __lshift__(self, other: object) -> Integer: 

715 result = int.__lshift__(self, other) # type: ignore[operator] 

716 if result is NotImplemented: 

717 return result # type: ignore[return-value] 

718 return self._new(result) 

719 

720 __lt__ = int.__lt__ 

721 

722 def __mod__(self, other: object) -> Integer: 

723 result = int.__mod__(self, other) # type: ignore[operator] 

724 if result is NotImplemented: 

725 return result # type: ignore[return-value] 

726 return self._new(result) 

727 

728 def __mul__(self, other: object) -> Integer: 

729 result = int.__mul__(self, other) # type: ignore[operator] 

730 if result is NotImplemented: 

731 return result # type: ignore[return-value] 

732 return self._new(result) 

733 

734 def __neg__(self) -> Integer: 

735 return self._new(int.__neg__(self)) 

736 

737 def __or__(self, other: object) -> Integer: 

738 result = int.__or__(self, other) # type: ignore[operator] 

739 if result is NotImplemented: 

740 return result # type: ignore[return-value] 

741 return self._new(result) 

742 

743 def __pos__(self) -> Integer: 

744 return self._new(int.__pos__(self)) 

745 

746 def __pow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[override] 

747 result = ( 

748 int.__pow__(self, other) if mod is None else int.__pow__(self, other, mod) 

749 ) 

750 return self._new(result) 

751 

752 def __radd__(self, other: object) -> Integer: 

753 result = int.__radd__(self, other) # type: ignore[operator] 

754 if result is NotImplemented: 

755 return result # type: ignore[return-value] 

756 return self._new(result) 

757 

758 def __rand__(self, other: object) -> Integer: 

759 result = int.__rand__(self, other) # type: ignore[operator] 

760 if result is NotImplemented: 

761 return result # type: ignore[return-value] 

762 return self._new(result) 

763 

764 def __rfloordiv__(self, other: object) -> Integer: 

765 result = int.__rfloordiv__(self, other) # type: ignore[operator] 

766 if result is NotImplemented: 

767 return result # type: ignore[return-value] 

768 return self._new(result) 

769 

770 def __rlshift__(self, other: object) -> Integer: 

771 result = int.__rlshift__(self, other) # type: ignore[operator] 

772 if result is NotImplemented: 

773 return result # type: ignore[return-value] 

774 return self._new(result) 

775 

776 def __rmod__(self, other: object) -> Integer: 

777 result = int.__rmod__(self, other) # type: ignore[operator] 

778 if result is NotImplemented: 

779 return result # type: ignore[return-value] 

780 return self._new(result) 

781 

782 def __rmul__(self, other: object) -> Integer: 

783 result = int.__rmul__(self, other) # type: ignore[operator] 

784 if result is NotImplemented: 

785 return result # type: ignore[return-value] 

786 return self._new(result) 

787 

788 def __ror__(self, other: object) -> Integer: 

789 result = int.__ror__(self, other) # type: ignore[operator] 

790 if result is NotImplemented: 

791 return result # type: ignore[return-value] 

792 return self._new(result) 

793 

794 def __round__(self, ndigits: int = 0) -> Integer: # type: ignore[override] 

795 return self._new(int.__round__(self, ndigits)) 

796 

797 def __rpow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[misc] 

798 result = ( 

799 int.__rpow__(self, other) if mod is None else int.__rpow__(self, other, mod) 

800 ) 

801 return self._new(result) 

802 

803 def __rrshift__(self, other: object) -> Integer: 

804 result = int.__rrshift__(self, other) # type: ignore[operator] 

805 if result is NotImplemented: 

806 return result # type: ignore[return-value] 

807 return self._new(result) 

808 

809 def __rshift__(self, other: object) -> Integer: 

810 result = int.__rshift__(self, other) # type: ignore[operator] 

811 if result is NotImplemented: 

812 return result # type: ignore[return-value] 

813 return self._new(result) 

814 

815 def __rxor__(self, other: object) -> Integer: 

816 result = int.__rxor__(self, other) # type: ignore[operator] 

817 if result is NotImplemented: 

818 return result # type: ignore[return-value] 

819 return self._new(result) 

820 

821 def __sub__(self, other: object) -> Integer: 

822 result = int.__sub__(self, other) # type: ignore[operator] 

823 if result is NotImplemented: 

824 return result # type: ignore[return-value] 

825 return self._new(result) 

826 

827 def __rsub__(self, other: object) -> Integer: 

828 result = int.__rsub__(self, other) # type: ignore[operator] 

829 if result is NotImplemented: 

830 return result # type: ignore[return-value] 

831 return self._new(result) 

832 

833 def __trunc__(self) -> Integer: 

834 return self._new(int.__trunc__(self)) 

835 

836 def __xor__(self, other: object) -> Integer: 

837 result = int.__xor__(self, other) # type: ignore[operator] 

838 if result is NotImplemented: 

839 return result # type: ignore[return-value] 

840 return self._new(result) 

841 

842 def __rtruediv__(self, other: object) -> Float: 

843 result = int.__rtruediv__(self, other) # type: ignore[operator] 

844 if result is NotImplemented: 

845 return result # type: ignore[return-value] 

846 return Float._new(self, result) # type: ignore[arg-type] 

847 

848 def __truediv__(self, other: object) -> Float: 

849 result = int.__truediv__(self, other) # type: ignore[operator] 

850 if result is NotImplemented: 

851 return result # type: ignore[return-value] 

852 return Float._new(self, result) # type: ignore[arg-type] 

853 

854 

855class Float(Item, _CustomFloat): 

856 """ 

857 A float literal. 

858 """ 

859 

860 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float: 

861 return float.__new__(cls, value) 

862 

863 def __init__(self, value: float, trivia: Trivia, raw: str) -> None: 

864 super().__init__(trivia) 

865 self._original = value 

866 self._raw = raw 

867 self._sign = False 

868 

869 if re.match(r"^[+\-].+$", raw): 

870 self._sign = True 

871 

872 def unwrap(self) -> float: 

873 return self._original 

874 

875 __float__ = unwrap 

876 

877 def __hash__(self) -> int: 

878 return hash(self.unwrap()) 

879 

880 @property 

881 def discriminant(self) -> int: 

882 return 3 

883 

884 @property 

885 def value(self) -> float: 

886 """The wrapped float value""" 

887 return self 

888 

889 def as_string(self) -> str: 

890 return self._raw 

891 

892 def _new(self, result: float) -> Float: 

893 raw = str(result) 

894 

895 # -0.0 >= 0 is True, but str(-0.0) already carries its sign. 

896 if self._sign and result >= 0 and not raw.startswith("-"): 

897 raw = f"+{raw}" 

898 

899 return Float(result, self._trivia.copy(), raw) 

900 

901 def _getstate(self, protocol: int = 3) -> tuple[float, Trivia, str]: 

902 return float(self), self._trivia, self._raw 

903 

904 # float methods — explicit typed wrappers 

905 def __abs__(self) -> Float: 

906 return self._new(float.__abs__(self)) 

907 

908 def __add__(self, other: object) -> Float: 

909 result = float.__add__(self, other) # type: ignore[operator] 

910 if result is NotImplemented: 

911 return result # type: ignore[return-value] 

912 return self._new(result) 

913 

914 __eq__ = float.__eq__ 

915 

916 def __floordiv__(self, other: object) -> Float: 

917 result = float.__floordiv__(self, other) # type: ignore[operator] 

918 if result is NotImplemented: 

919 return result # type: ignore[return-value] 

920 return self._new(result) 

921 

922 __le__ = float.__le__ 

923 __lt__ = float.__lt__ 

924 

925 def __mod__(self, other: object) -> Float: 

926 result = float.__mod__(self, other) # type: ignore[operator] 

927 if result is NotImplemented: 

928 return result # type: ignore[return-value] 

929 return self._new(result) 

930 

931 def __mul__(self, other: object) -> Float: 

932 result = float.__mul__(self, other) # type: ignore[operator] 

933 if result is NotImplemented: 

934 return result # type: ignore[return-value] 

935 return self._new(result) 

936 

937 def __neg__(self) -> Float: 

938 return self._new(float.__neg__(self)) 

939 

940 def __pos__(self) -> Float: 

941 return self._new(float.__pos__(self)) 

942 

943 def __pow__(self, other: object, mod: None = None) -> Float: 

944 result = float.__pow__(self, other) # type: ignore[operator] 

945 if result is NotImplemented: 

946 return result # type: ignore[no-any-return] 

947 return self._new(result) 

948 

949 def __radd__(self, other: object) -> Float: 

950 result = float.__radd__(self, other) # type: ignore[operator] 

951 if result is NotImplemented: 

952 return result # type: ignore[return-value] 

953 return self._new(result) 

954 

955 def __rfloordiv__(self, other: object) -> Float: 

956 result = float.__rfloordiv__(self, other) # type: ignore[operator] 

957 if result is NotImplemented: 

958 return result # type: ignore[return-value] 

959 return self._new(result) 

960 

961 def __rmod__(self, other: object) -> Float: 

962 result = float.__rmod__(self, other) # type: ignore[operator] 

963 if result is NotImplemented: 

964 return result # type: ignore[return-value] 

965 return self._new(result) 

966 

967 def __rmul__(self, other: object) -> Float: 

968 result = float.__rmul__(self, other) # type: ignore[operator] 

969 if result is NotImplemented: 

970 return result # type: ignore[return-value] 

971 return self._new(result) 

972 

973 def __round__(self, ndigits: int = 0) -> Float: # type: ignore[override] 

974 return self._new(float.__round__(self, ndigits)) 

975 

976 def __rpow__(self, other: object, mod: None = None) -> Float: 

977 result = float.__rpow__(self, other) # type: ignore[operator] 

978 if result is NotImplemented: 

979 return result # type: ignore[no-any-return] 

980 return self._new(result) 

981 

982 def __rtruediv__(self, other: object) -> Float: 

983 result = float.__rtruediv__(self, other) # type: ignore[operator] 

984 if result is NotImplemented: 

985 return result # type: ignore[return-value] 

986 return self._new(result) 

987 

988 def __truediv__(self, other: object) -> Float: 

989 result = float.__truediv__(self, other) # type: ignore[operator] 

990 if result is NotImplemented: 

991 return result # type: ignore[return-value] 

992 return self._new(result) 

993 

994 def __sub__(self, other: object) -> Float: 

995 result = float.__sub__(self, other) # type: ignore[operator] 

996 if result is NotImplemented: 

997 return result # type: ignore[return-value] 

998 return self._new(result) 

999 

1000 def __rsub__(self, other: object) -> Float: 

1001 result = float.__rsub__(self, other) # type: ignore[operator] 

1002 if result is NotImplemented: 

1003 return result # type: ignore[return-value] 

1004 return self._new(result) 

1005 

1006 __trunc__ = float.__trunc__ 

1007 __ceil__ = float.__ceil__ 

1008 __floor__ = float.__floor__ 

1009 

1010 

1011class Bool(Item): 

1012 """ 

1013 A boolean literal. 

1014 """ 

1015 

1016 def __init__(self, t: int | BoolType, trivia: Trivia) -> None: 

1017 super().__init__(trivia) 

1018 

1019 self._value = bool(t) 

1020 

1021 def unwrap(self) -> bool: 

1022 return bool(self) 

1023 

1024 @property 

1025 def discriminant(self) -> int: 

1026 return 4 

1027 

1028 @property 

1029 def value(self) -> bool: 

1030 """The wrapped boolean value""" 

1031 return self._value 

1032 

1033 def as_string(self) -> str: 

1034 return str(self._value).lower() 

1035 

1036 def _getstate(self, protocol: int = 3) -> tuple[bool, Trivia]: 

1037 return self._value, self._trivia 

1038 

1039 def __bool__(self) -> bool: 

1040 return self._value 

1041 

1042 __nonzero__ = __bool__ 

1043 

1044 def __eq__(self, other: object) -> bool: 

1045 if isinstance(other, Bool): 

1046 other = other.value 

1047 

1048 if not isinstance(other, bool): 

1049 return NotImplemented 

1050 

1051 return other == self._value 

1052 

1053 def __hash__(self) -> int: 

1054 return hash(self._value) 

1055 

1056 def __repr__(self) -> str: 

1057 return repr(self._value) 

1058 

1059 

1060class DateTime(Item, datetime): 

1061 """ 

1062 A datetime literal. 

1063 """ 

1064 

1065 def __new__( 

1066 cls, 

1067 year: int, 

1068 month: int, 

1069 day: int, 

1070 hour: int, 

1071 minute: int, 

1072 second: int, 

1073 microsecond: int, 

1074 tzinfo: tzinfo | None, 

1075 trivia: Trivia | None = None, 

1076 raw: str | None = None, 

1077 fold: int = 0, 

1078 **kwargs: object, 

1079 ) -> DateTime: 

1080 return datetime.__new__( 

1081 cls, 

1082 year, 

1083 month, 

1084 day, 

1085 hour, 

1086 minute, 

1087 second, 

1088 microsecond, 

1089 tzinfo=tzinfo, 

1090 fold=fold, 

1091 ) 

1092 

1093 def __init__( 

1094 self, 

1095 year: int, 

1096 month: int, 

1097 day: int, 

1098 hour: int, 

1099 minute: int, 

1100 second: int, 

1101 microsecond: int, 

1102 tzinfo: tzinfo | None, 

1103 trivia: Trivia | None = None, 

1104 raw: str | None = None, 

1105 fold: int = 0, 

1106 **kwargs: object, 

1107 ) -> None: 

1108 super().__init__(trivia or Trivia()) 

1109 

1110 self._raw = raw or self.isoformat() 

1111 

1112 def unwrap(self) -> datetime: 

1113 ( 

1114 year, 

1115 month, 

1116 day, 

1117 hour, 

1118 minute, 

1119 second, 

1120 microsecond, 

1121 tzinfo, 

1122 _, 

1123 _, 

1124 ) = self._getstate() 

1125 return datetime( 

1126 year, month, day, hour, minute, second, microsecond, tzinfo, fold=self.fold 

1127 ) 

1128 

1129 @property 

1130 def discriminant(self) -> int: 

1131 return 5 

1132 

1133 @property 

1134 def value(self) -> datetime: 

1135 return self 

1136 

1137 def as_string(self) -> str: 

1138 return self._raw 

1139 

1140 def __add__(self, other: timedelta) -> DateTime: 

1141 if PY38: 

1142 result = datetime( 

1143 self.year, 

1144 self.month, 

1145 self.day, 

1146 self.hour, 

1147 self.minute, 

1148 self.second, 

1149 self.microsecond, 

1150 self.tzinfo, 

1151 ).__add__(other) 

1152 else: 

1153 result = super().__add__(other) 

1154 

1155 return self._new(result) 

1156 

1157 @overload # type: ignore[override] 

1158 def __sub__(self, other: timedelta) -> DateTime: ... 

1159 

1160 @overload 

1161 def __sub__(self, other: datetime) -> timedelta: ... 

1162 

1163 def __sub__(self, other: timedelta | datetime) -> DateTime | timedelta: 

1164 if PY38: 

1165 result = datetime( 

1166 self.year, 

1167 self.month, 

1168 self.day, 

1169 self.hour, 

1170 self.minute, 

1171 self.second, 

1172 self.microsecond, 

1173 self.tzinfo, 

1174 fold=self.fold, 

1175 ).__sub__(other) 

1176 else: 

1177 result = super().__sub__(other) # type: ignore[operator] 

1178 

1179 if isinstance(result, datetime): 

1180 result = self._new(result) 

1181 

1182 return result 

1183 

1184 def replace(self, *args: object, **kwargs: object) -> DateTime: 

1185 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] 

1186 

1187 def astimezone(self, tz: tzinfo) -> DateTime: # type: ignore[override] 

1188 result = super().astimezone(tz) 

1189 if PY38: 

1190 return result 

1191 return self._new(result) 

1192 

1193 def _new(self, result: datetime) -> DateTime: 

1194 raw = result.isoformat() 

1195 

1196 return DateTime( 

1197 result.year, 

1198 result.month, 

1199 result.day, 

1200 result.hour, 

1201 result.minute, 

1202 result.second, 

1203 result.microsecond, 

1204 result.tzinfo, 

1205 self._trivia.copy(), 

1206 raw, 

1207 fold=result.fold, 

1208 ) 

1209 

1210 def _getstate( 

1211 self, protocol: int = 3 

1212 ) -> tuple[int, int, int, int, int, int, int, tzinfo | None, Trivia, str]: 

1213 return ( 

1214 self.year, 

1215 self.month, 

1216 self.day, 

1217 self.hour, 

1218 self.minute, 

1219 self.second, 

1220 self.microsecond, 

1221 self.tzinfo, 

1222 self._trivia, 

1223 self._raw, 

1224 ) 

1225 

1226 

1227class Date(Item, date): 

1228 """ 

1229 A date literal. 

1230 """ 

1231 

1232 def __new__( 

1233 cls, 

1234 year: int, 

1235 month: int, 

1236 day: int, 

1237 trivia: Trivia | None = None, 

1238 raw: str = "", 

1239 ) -> Date: 

1240 return date.__new__(cls, year, month, day) 

1241 

1242 def __init__( 

1243 self, 

1244 year: int, 

1245 month: int, 

1246 day: int, 

1247 trivia: Trivia | None = None, 

1248 raw: str = "", 

1249 ) -> None: 

1250 super().__init__(trivia or Trivia()) 

1251 

1252 self._raw = raw 

1253 

1254 def unwrap(self) -> date: 

1255 (year, month, day, _, _) = self._getstate() 

1256 return date(year, month, day) 

1257 

1258 @property 

1259 def discriminant(self) -> int: 

1260 return 6 

1261 

1262 @property 

1263 def value(self) -> date: 

1264 return self 

1265 

1266 def as_string(self) -> str: 

1267 return self._raw 

1268 

1269 def __add__(self, other: timedelta) -> Date: 

1270 if PY38: 

1271 result = date(self.year, self.month, self.day).__add__(other) 

1272 else: 

1273 result = super().__add__(other) 

1274 

1275 return self._new(result) 

1276 

1277 @overload # type: ignore[override] 

1278 def __sub__(self, other: timedelta) -> Date: ... 

1279 

1280 @overload 

1281 def __sub__(self, other: date) -> timedelta: ... 

1282 

1283 def __sub__(self, other: timedelta | date) -> Date | timedelta: 

1284 if PY38: 

1285 result = date(self.year, self.month, self.day).__sub__(other) 

1286 else: 

1287 result = super().__sub__(other) # type: ignore[operator] 

1288 

1289 if isinstance(result, date): 

1290 result = self._new(result) 

1291 

1292 return result 

1293 

1294 def replace(self, *args: object, **kwargs: object) -> Date: 

1295 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] 

1296 

1297 def _new(self, result: date) -> Date: 

1298 raw = result.isoformat() 

1299 

1300 return Date(result.year, result.month, result.day, self._trivia.copy(), raw) 

1301 

1302 def _getstate(self, protocol: int = 3) -> tuple[int, int, int, Trivia, str]: 

1303 return (self.year, self.month, self.day, self._trivia, self._raw) 

1304 

1305 

1306class Time(Item, time): 

1307 """ 

1308 A time literal. 

1309 """ 

1310 

1311 def __new__( 

1312 cls, 

1313 hour: int, 

1314 minute: int, 

1315 second: int, 

1316 microsecond: int, 

1317 tzinfo: tzinfo | None, 

1318 trivia: Trivia | None = None, 

1319 raw: str = "", 

1320 fold: int = 0, 

1321 ) -> Time: 

1322 return time.__new__(cls, hour, minute, second, microsecond, tzinfo, fold=fold) 

1323 

1324 def __init__( 

1325 self, 

1326 hour: int, 

1327 minute: int, 

1328 second: int, 

1329 microsecond: int, 

1330 tzinfo: tzinfo | None, 

1331 trivia: Trivia | None = None, 

1332 raw: str = "", 

1333 fold: int = 0, 

1334 ) -> None: 

1335 super().__init__(trivia or Trivia()) 

1336 

1337 self._raw = raw 

1338 

1339 def unwrap(self) -> time: 

1340 (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate() 

1341 return time(hour, minute, second, microsecond, tzinfo, fold=self.fold) 

1342 

1343 @property 

1344 def discriminant(self) -> int: 

1345 return 7 

1346 

1347 @property 

1348 def value(self) -> time: 

1349 return self 

1350 

1351 def as_string(self) -> str: 

1352 return self._raw 

1353 

1354 def replace(self, *args: object, **kwargs: object) -> Time: 

1355 return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] 

1356 

1357 def _new(self, result: time) -> Time: 

1358 raw = _format_time(result) 

1359 

1360 return Time( 

1361 result.hour, 

1362 result.minute, 

1363 result.second, 

1364 result.microsecond, 

1365 result.tzinfo, 

1366 self._trivia.copy(), 

1367 raw, 

1368 fold=result.fold, 

1369 ) 

1370 

1371 def _getstate( 

1372 self, protocol: int = 3 

1373 ) -> tuple[int, int, int, int, tzinfo | None, Trivia, str]: 

1374 return ( 

1375 self.hour, 

1376 self.minute, 

1377 self.second, 

1378 self.microsecond, 

1379 self.tzinfo, 

1380 self._trivia, 

1381 self._raw, 

1382 ) 

1383 

1384 

1385class _ArrayItemGroup: 

1386 __slots__ = ("comma", "comment", "indent", "value") 

1387 

1388 def __init__( 

1389 self, 

1390 value: Item | None = None, 

1391 indent: Whitespace | None = None, 

1392 comma: Whitespace | None = None, 

1393 comment: Comment | None = None, 

1394 ) -> None: 

1395 self.value = value 

1396 self.indent = indent 

1397 self.comma = comma 

1398 self.comment = comment 

1399 

1400 def __iter__(self) -> Iterator[Item]: 

1401 return ( 

1402 x 

1403 for x in (self.indent, self.value, self.comma, self.comment) 

1404 if x is not None 

1405 ) 

1406 

1407 def __repr__(self) -> str: 

1408 return repr(tuple(self)) 

1409 

1410 def is_whitespace(self) -> bool: 

1411 return self.value is None and self.comment is None 

1412 

1413 def __bool__(self) -> bool: 

1414 try: 

1415 next(iter(self)) 

1416 except StopIteration: 

1417 return False 

1418 return True 

1419 

1420 

1421class Array(Item, _CustomList): # type: ignore[type-arg] 

1422 """ 

1423 An array literal 

1424 """ 

1425 

1426 def __init__( 

1427 self, value: list[Item], trivia: Trivia, multiline: bool = False 

1428 ) -> None: 

1429 super().__init__(trivia) 

1430 list.__init__( 

1431 self, 

1432 [v for v in value if not isinstance(v, (Whitespace, Comment, Null))], 

1433 ) 

1434 self._index_map: dict[int, int] = {} 

1435 self._value = self._group_values(value) 

1436 self._multiline = multiline 

1437 self._reindex() 

1438 

1439 def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]: 

1440 """Group the values into (indent, value, comma, comment) tuples""" 

1441 groups = [] 

1442 this_group = _ArrayItemGroup() 

1443 start_new_group = False 

1444 for item in value: 

1445 if isinstance(item, Whitespace): 

1446 if "," not in item.s or start_new_group: 

1447 groups.append(this_group) 

1448 this_group = _ArrayItemGroup(indent=item) 

1449 start_new_group = False 

1450 else: 

1451 if this_group.value is None: 

1452 # when comma is met and no value is provided, add a dummy Null 

1453 this_group.value = Null() 

1454 this_group.comma = item 

1455 elif isinstance(item, Comment): 

1456 if this_group.value is None: 

1457 this_group.value = Null() 

1458 this_group.comment = item 

1459 # Comments are the last item in a group. 

1460 start_new_group = True 

1461 elif this_group.value is None: 

1462 this_group.value = item 

1463 else: 

1464 groups.append(this_group) 

1465 this_group = _ArrayItemGroup(value=item) 

1466 groups.append(this_group) 

1467 return [group for group in groups if group] 

1468 

1469 def unwrap(self) -> list[Any]: 

1470 unwrapped = [] 

1471 for v in self: 

1472 if hasattr(v, "unwrap"): 

1473 unwrapped.append(v.unwrap()) 

1474 else: 

1475 unwrapped.append(v) 

1476 return unwrapped 

1477 

1478 @property 

1479 def discriminant(self) -> int: 

1480 return 8 

1481 

1482 @property 

1483 def value(self) -> list[Item]: 

1484 return self 

1485 

1486 def _iter_items(self) -> Iterator[Item]: 

1487 for v in self._value: 

1488 yield from v 

1489 

1490 def multiline(self, multiline: bool) -> Array: 

1491 """Change the array to display in multiline or not. 

1492 

1493 :Example: 

1494 

1495 >>> a = item([1, 2, 3]) 

1496 >>> print(a.as_string()) 

1497 [1, 2, 3] 

1498 >>> print(a.multiline(True).as_string()) 

1499 [ 

1500 1, 

1501 2, 

1502 3, 

1503 ] 

1504 """ 

1505 self._multiline = multiline 

1506 

1507 return self 

1508 

1509 def as_string(self) -> str: 

1510 if not self._multiline or not self._value: 

1511 rendered = list(self._iter_items()) 

1512 inner = "".join(v.as_string() for v in rendered) 

1513 if rendered and isinstance(rendered[-1], Comment): 

1514 # A trailing comment (e.g. from add_line(comment=...)) carries 

1515 # no newline, so keep the closing bracket off the comment line 

1516 # to produce parseable output (GH #580). 

1517 inner += "\n" + self.trivia.indent 

1518 return f"[{inner}]" 

1519 

1520 s = "[\n" 

1521 s += "".join( 

1522 self.trivia.indent 

1523 + " " * 4 

1524 + v.value.as_string() 

1525 + ("," if not isinstance(v.value, Null) else "") 

1526 + (v.comment.as_string() if v.comment is not None else "") 

1527 + "\n" 

1528 for v in self._value 

1529 if v.value is not None 

1530 ) 

1531 s += self.trivia.indent + "]" 

1532 

1533 return s 

1534 

1535 def _reindex(self) -> None: 

1536 self._index_map.clear() 

1537 index = 0 

1538 for i, v in enumerate(self._value): 

1539 if v.value is None or isinstance(v.value, Null): 

1540 continue 

1541 self._index_map[index] = i 

1542 index += 1 

1543 

1544 def add_line( 

1545 self, 

1546 *items: Any, 

1547 indent: str = " ", 

1548 comment: str | None = None, 

1549 add_comma: bool = True, 

1550 newline: bool = True, 

1551 ) -> None: 

1552 """Add multiple items in a line to control the format precisely. 

1553 When add_comma is True, only accept actual values and 

1554 ", " will be added between values automatically. 

1555 

1556 :Example: 

1557 

1558 >>> a = array() 

1559 >>> a.add_line(1, 2, 3) 

1560 >>> a.add_line(4, 5, 6) 

1561 >>> a.add_line(indent="") 

1562 >>> print(a.as_string()) 

1563 [ 

1564 1, 2, 3, 

1565 4, 5, 6, 

1566 ] 

1567 """ 

1568 if comment and ("\n" in comment or "\r" in comment): 

1569 raise ValueError("Comment cannot contain line breaks") 

1570 new_values: list[Item] = [] 

1571 first_indent = f"\n{indent}" if newline else indent 

1572 if first_indent: 

1573 new_values.append(Whitespace(first_indent)) 

1574 whitespace = "" 

1575 data_values = [] 

1576 for i, el in enumerate(items): 

1577 it = item(el, _parent=self) 

1578 if isinstance(it, Comment) or (add_comma and isinstance(el, Whitespace)): 

1579 raise ValueError(f"item type {type(it)} is not allowed in add_line") 

1580 if not isinstance(it, Whitespace): 

1581 if whitespace: 

1582 new_values.append(Whitespace(whitespace)) 

1583 whitespace = "" 

1584 new_values.append(it) 

1585 data_values.append(it.value) 

1586 if add_comma: 

1587 new_values.append(Whitespace(",")) 

1588 if i != len(items) - 1: 

1589 new_values.append(Whitespace(" ")) 

1590 elif "," not in it.s: 

1591 whitespace += it.s 

1592 else: 

1593 new_values.append(it) 

1594 if whitespace: 

1595 new_values.append(Whitespace(whitespace)) 

1596 if comment: 

1597 indent = " " if items else "" 

1598 new_values.append( 

1599 Comment(Trivia(indent=indent, comment=f"# {comment}", trail="")) 

1600 ) 

1601 list.extend(self, data_values) 

1602 if len(self._value) > 0: 

1603 last_item = self._value[-1] 

1604 last_value_item = next( 

1605 ( 

1606 v 

1607 for v in self._value[::-1] 

1608 if v.value is not None and not isinstance(v.value, Null) 

1609 ), 

1610 None, 

1611 ) 

1612 if last_value_item is not None: 

1613 last_value_item.comma = Whitespace(",") 

1614 if last_item.is_whitespace(): 

1615 self._value[-1:-1] = self._group_values(new_values) 

1616 else: 

1617 self._value.extend(self._group_values(new_values)) 

1618 else: 

1619 self._value.extend(self._group_values(new_values)) 

1620 self._reindex() 

1621 

1622 def clear(self) -> None: 

1623 """Clear the array.""" 

1624 list.clear(self) 

1625 self._index_map.clear() 

1626 self._value.clear() 

1627 

1628 def __len__(self) -> int: 

1629 return list.__len__(self) 

1630 

1631 def item(self, index: int) -> Item: 

1632 return list.__getitem__(self, index) # type: ignore[no-any-return] 

1633 

1634 def __getitem__(self, key: int | slice) -> Any: # type: ignore[override] 

1635 return list.__getitem__(self, key) 

1636 

1637 def __setitem__(self, key: int | slice, value: Any) -> None: # type: ignore[override] 

1638 if isinstance(key, slice): 

1639 raise ValueError("slice assignment is not supported") 

1640 it = item(value, _parent=self) 

1641 list.__setitem__(self, key, it) 

1642 if key < 0: 

1643 key += len(self) 

1644 self._value[self._index_map[key]].value = it 

1645 

1646 def insert(self, pos: int, value: Any) -> None: # type: ignore[override] 

1647 it = item(value, _parent=self) 

1648 length = len(self) 

1649 if not isinstance(it, (Comment, Whitespace)): 

1650 list.insert(self, pos, it) 

1651 if pos < 0: 

1652 pos += length 

1653 if pos < 0: 

1654 pos = 0 

1655 

1656 idx = 0 # insert position of the self._value list 

1657 default_indent = " " 

1658 if pos < length: 

1659 try: 

1660 idx = self._index_map[pos] 

1661 except KeyError as e: 

1662 raise IndexError("list index out of range") from e 

1663 else: 

1664 idx = len(self._value) 

1665 if idx >= 1 and self._value[idx - 1].is_whitespace(): 

1666 # The last item is a pure whitespace(\n ), insert before it 

1667 idx -= 1 

1668 _indent = self._value[idx].indent 

1669 if _indent is not None and "\n" in _indent.s: 

1670 default_indent = "\n " 

1671 indent: Whitespace | None = None 

1672 comma: Whitespace | None = Whitespace(",") if pos < length else None 

1673 if idx < len(self._value) and not self._value[idx].is_whitespace(): 

1674 # Prefer to copy the indentation from the item after 

1675 indent = self._value[idx].indent 

1676 if idx > 0: 

1677 last_item = self._value[idx - 1] 

1678 if indent is None: 

1679 indent = last_item.indent 

1680 if not isinstance(last_item.value, Null) and "\n" in default_indent: 

1681 # Copy the comma from the last item if 1) it contains a value and 

1682 # 2) the array is multiline 

1683 comma = last_item.comma 

1684 comma_in_indent = indent is not None and "," in indent.s 

1685 if ( 

1686 last_item.comma is None 

1687 and not isinstance(last_item.value, Null) 

1688 and not comma_in_indent 

1689 ): 

1690 # Add comma to the last item to separate it from the following 

1691 # items, unless the new item's indent already starts with a 

1692 # comma (comma-first style). 

1693 last_item.comma = Whitespace(",") 

1694 if indent is not None and "," in indent.s: 

1695 # Comma-first style: the item that will follow the new one brings 

1696 # its own leading comma, so the new item must not add a trailing 

1697 # comma of its own. 

1698 comma = None 

1699 if indent is None and (idx > 0 or "\n" in default_indent): 

1700 # apply default indent if it isn't the first item or the array is multiline. 

1701 indent = Whitespace(default_indent) 

1702 new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma) 

1703 self._value.insert(idx, new_item) 

1704 self._reindex() 

1705 

1706 def __delitem__(self, key: int | slice) -> None: # type: ignore[override] 

1707 length = len(self) 

1708 list.__delitem__(self, key) 

1709 

1710 if isinstance(key, slice): 

1711 indices_to_remove = list(range(*key.indices(length))) 

1712 else: 

1713 indices_to_remove = [length + key if key < 0 else key] 

1714 for i in sorted(indices_to_remove, reverse=True): 

1715 try: 

1716 idx = self._index_map[i] 

1717 except KeyError as e: 

1718 if not isinstance(key, slice): 

1719 raise IndexError("list index out of range") from e 

1720 else: 

1721 group_rm = self._value[idx] 

1722 del self._value[idx] 

1723 if ( 

1724 idx == 0 

1725 and len(self._value) > 0 

1726 and (ind := self._value[idx].indent) 

1727 and "\n" not in ind.s 

1728 ): 

1729 # Remove the indentation of the first item if not newline 

1730 self._value[idx].indent = None 

1731 comma_in_indent = ( 

1732 group_rm.indent is not None and "," in group_rm.indent.s 

1733 ) 

1734 comma_in_comma = group_rm.comma is not None and "," in group_rm.comma.s 

1735 if comma_in_indent and comma_in_comma: 

1736 # Removed group had both commas. Add one to the next group. 

1737 group = self._value[idx] if len(self._value) > idx else None 

1738 if group is not None: 

1739 if group.indent is None: 

1740 group.indent = Whitespace(",") 

1741 elif "," not in group.indent.s: 

1742 # Insert the comma after the newline 

1743 try: 

1744 newline_index = group.indent.s.index("\n") 

1745 group.indent._s = ( 

1746 group.indent.s[: newline_index + 1] 

1747 + "," 

1748 + group.indent.s[newline_index + 1 :] 

1749 ) 

1750 except ValueError: 

1751 group.indent._s = "," + group.indent.s 

1752 elif not comma_in_indent and not comma_in_comma: 

1753 # Removed group had no commas. Remove the next comma found. 

1754 for j in range(idx, len(self._value)): 

1755 group = self._value[j] 

1756 if group.indent is not None and "," in group.indent.s: 

1757 group.indent._s = group.indent.s.replace(",", "", 1) 

1758 break 

1759 if group_rm.indent is not None and "\n" in group_rm.indent.s: 

1760 # Restore the removed group's newline onto the next group 

1761 # if the next group does not have a newline. 

1762 # i.e. the two were on the same line 

1763 group = self._value[idx] if len(self._value) > idx else None 

1764 if group is not None and ( 

1765 group.indent is None or "\n" not in group.indent.s 

1766 ): 

1767 group.indent = group_rm.indent 

1768 

1769 if len(self._value) > 0: 

1770 v = self._value[-1] 

1771 if not v.is_whitespace(): 

1772 # remove the comma of the last item 

1773 v.comma = None 

1774 

1775 self._reindex() 

1776 

1777 def _getstate(self, protocol: int = 3) -> tuple[list[Item], Trivia, bool]: 

1778 return list(self._iter_items()), self._trivia, self._multiline 

1779 

1780 

1781class AbstractTable(Item, _CustomDict): # type: ignore[type-arg] 

1782 """Common behaviour of both :class:`Table` and :class:`InlineTable`""" 

1783 

1784 def __init__(self, value: container.Container, trivia: Trivia): 

1785 Item.__init__(self, trivia) 

1786 

1787 self._value = value 

1788 

1789 for k, v in self._value.body: 

1790 if k is not None: 

1791 dict.__setitem__(self, k.key, v) 

1792 

1793 def unwrap(self) -> dict[str, Any]: 

1794 # Delegate to the inner container's unwrap, which walks its _body 

1795 # directly instead of re-resolving every key through items()/__getitem__ 

1796 # (which rebuilds a SingleKey, and an OutOfOrderTableProxy for 

1797 # out-of-order keys, per key just to throw it away). 

1798 return self._value.unwrap() 

1799 

1800 @property 

1801 def value(self) -> container.Container: 

1802 return self._value 

1803 

1804 @overload 

1805 def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ... 

1806 

1807 @overload 

1808 def append(self: AT, key: Key | str, value: Any) -> AT: ... 

1809 

1810 def append(self: AT, key: Key | str | None, value: Any) -> AT: 

1811 raise NotImplementedError 

1812 

1813 @overload 

1814 def add(self: AT, key: Comment | Whitespace) -> AT: ... 

1815 

1816 @overload 

1817 def add(self: AT, key: Key | str, value: Any = ...) -> AT: ... 

1818 

1819 def add( 

1820 self: AT, key: Key | str | Comment | Whitespace, value: Any | None = None 

1821 ) -> AT: 

1822 if value is None: 

1823 if not isinstance(key, (Comment, Whitespace)): 

1824 msg = "Non comment/whitespace items must have an associated key" 

1825 raise ValueError(msg) 

1826 

1827 return self.append(None, key) 

1828 

1829 if isinstance(key, (Comment, Whitespace)): 

1830 raise ValueError("Comment/Whitespace keys must not have a value") 

1831 

1832 return self.append(key, value) 

1833 

1834 def remove(self: AT, key: Key | str) -> AT: 

1835 self._value.remove(key) 

1836 

1837 if isinstance(key, Key): 

1838 key = key.key 

1839 

1840 if key is not None: 

1841 dict.__delitem__(self, key) 

1842 

1843 return self 

1844 

1845 def item(self, key: Key | str) -> Item | OutOfOrderTableProxy: 

1846 return self._value.item(key) 

1847 

1848 def setdefault(self, key: Key | str, default: Any) -> Any: # type: ignore[override] 

1849 super().setdefault(key, default) 

1850 return self[key] 

1851 

1852 def __str__(self) -> str: 

1853 return str(self.value) 

1854 

1855 def copy(self: AT) -> AT: 

1856 return copy.copy(self) 

1857 

1858 def __repr__(self) -> str: 

1859 return repr(self.value) 

1860 

1861 def __iter__(self) -> Iterator[str]: 

1862 return iter(self._value) 

1863 

1864 def __len__(self) -> int: 

1865 return len(self._value) 

1866 

1867 def __delitem__(self, key: Key | str) -> None: 

1868 self.remove(key) 

1869 

1870 def __getitem__(self, key: Key | str) -> Any: 

1871 return self._value[key] 

1872 

1873 def __contains__(self, key: object) -> bool: 

1874 # Native membership test. The inherited ``MutableMapping.__contains__`` 

1875 # resolves the value via ``__getitem__`` (``self._value[key]``) -- and 

1876 # builds a ``NonExistentKey`` on every absent key -- only to discard it. 

1877 # Delegate straight to the inner container instead: its ``__contains__`` 

1878 # answers from ``_map`` while still building the ``OutOfOrderTableProxy`` 

1879 # for an out-of-order entry, so validation runs exactly as before. 

1880 return key in self._value 

1881 

1882 def __setitem__(self, key: Key | str, value: Any) -> None: 

1883 if not isinstance(value, Item): 

1884 value = item(value, _parent=self) 

1885 

1886 is_replace = key in self 

1887 self._value[key] = value 

1888 

1889 if key is not None: 

1890 dict.__setitem__(self, key, value) 

1891 

1892 if is_replace: 

1893 return 

1894 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) 

1895 if not m: 

1896 return 

1897 

1898 indent = m.group(1) 

1899 

1900 if not isinstance(value, Whitespace): 

1901 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) 

1902 if not m: 

1903 value.trivia.indent = indent 

1904 else: 

1905 value.trivia.indent = m.group(1) + indent + m.group(2) 

1906 

1907 

1908class Table(AbstractTable): 

1909 """ 

1910 A table literal. 

1911 """ 

1912 

1913 def __init__( 

1914 self, 

1915 value: container.Container, 

1916 trivia: Trivia, 

1917 is_aot_element: bool, 

1918 is_super_table: bool | None = None, 

1919 name: str | None = None, 

1920 display_name: str | None = None, 

1921 ) -> None: 

1922 super().__init__(value, trivia) 

1923 

1924 self.name = name 

1925 self.display_name = display_name 

1926 self._is_aot_element = is_aot_element 

1927 self._is_super_table = is_super_table 

1928 

1929 @property 

1930 def discriminant(self) -> int: 

1931 return 9 

1932 

1933 def __copy__(self) -> Table: 

1934 return type(self)( 

1935 self._value.copy(), 

1936 self._trivia.copy(), 

1937 self._is_aot_element, 

1938 self._is_super_table, 

1939 self.name, 

1940 self.display_name, 

1941 ) 

1942 

1943 def append(self, key: Key | str | None, _item: Any) -> Table: 

1944 """ 

1945 Appends a (key, item) to the table. 

1946 """ 

1947 if not isinstance(_item, Item): 

1948 _item = item(_item, _parent=self) 

1949 

1950 self._value.append(key, _item) 

1951 

1952 if isinstance(key, Key): 

1953 key = next(iter(key)).key 

1954 _item = self._value[key] 

1955 

1956 if key is not None: 

1957 dict.__setitem__(self, key, _item) 

1958 

1959 m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent) 

1960 if not m: 

1961 return self 

1962 

1963 indent = m.group(1) 

1964 

1965 if not isinstance(_item, Whitespace): 

1966 m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent) 

1967 if not m: 

1968 _item.trivia.indent = indent 

1969 else: 

1970 _item.trivia.indent = m.group(1) + indent + m.group(2) 

1971 

1972 return self 

1973 

1974 def raw_append(self, key: Key | str | None, _item: Any) -> Table: 

1975 """Similar to :meth:`append` but does not copy indentation.""" 

1976 if not isinstance(_item, Item): 

1977 _item = item(_item) 

1978 

1979 self._value.append(key, _item, validate=False) 

1980 

1981 if isinstance(key, Key): 

1982 key = next(iter(key)).key 

1983 _item = self._value[key] 

1984 

1985 if key is not None: 

1986 dict.__setitem__(self, key, _item) 

1987 

1988 return self 

1989 

1990 def is_aot_element(self) -> bool: 

1991 """True if the table is the direct child of an AOT element.""" 

1992 return self._is_aot_element 

1993 

1994 def is_super_table(self) -> bool: 

1995 """A super table is the intermediate parent of a nested table as in [a.b.c]. 

1996 If true, it won't appear in the TOML representation.""" 

1997 if self._is_super_table is not None: 

1998 return self._is_super_table 

1999 if not self: 

2000 return False 

2001 # If the table has children and all children are tables, then it is a super table. 

2002 for k, child in self.items(): 

2003 if not isinstance(k, Key): 

2004 k = SingleKey(k) 

2005 index = self.value._map[k] 

2006 if isinstance(index, tuple): 

2007 return False 

2008 real_key = self.value.body[index][0] 

2009 if ( 

2010 not isinstance(child, (Table, AoT)) 

2011 or real_key is None 

2012 or real_key.is_dotted() 

2013 ): 

2014 return False 

2015 return True 

2016 

2017 def as_string(self) -> str: 

2018 return self._value.as_string() 

2019 

2020 # Helpers 

2021 

2022 def indent(self, indent: int) -> Table: 

2023 """Indent the table with given number of spaces.""" 

2024 super().indent(indent) 

2025 

2026 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) 

2027 if not m: 

2028 indent_str = "" 

2029 else: 

2030 indent_str = m.group(1) 

2031 

2032 for _, item in self._value.body: 

2033 if not isinstance(item, Whitespace): 

2034 item.trivia.indent = indent_str + item.trivia.indent 

2035 

2036 return self 

2037 

2038 def invalidate_display_name(self) -> None: 

2039 """Call ``invalidate_display_name`` on the contained tables""" 

2040 self.display_name = None 

2041 

2042 for child in self.values(): 

2043 if hasattr(child, "invalidate_display_name"): 

2044 child.invalidate_display_name() 

2045 

2046 def _getstate( 

2047 self, protocol: int = 3 

2048 ) -> tuple[container.Container, Trivia, bool, bool | None, str | None, str | None]: 

2049 return ( 

2050 self._value, 

2051 self._trivia, 

2052 self._is_aot_element, 

2053 self._is_super_table, 

2054 self.name, 

2055 self.display_name, 

2056 ) 

2057 

2058 

2059class InlineTable(AbstractTable): 

2060 """ 

2061 An inline table literal. 

2062 """ 

2063 

2064 def __init__( 

2065 self, value: container.Container, trivia: Trivia, new: bool = False 

2066 ) -> None: 

2067 super().__init__(value, trivia) 

2068 

2069 self._new = new 

2070 

2071 @property 

2072 def discriminant(self) -> int: 

2073 return 10 

2074 

2075 def append(self, key: Key | str | None, _item: Any) -> InlineTable: 

2076 """ 

2077 Appends a (key, item) to the table. 

2078 """ 

2079 if not isinstance(_item, Item): 

2080 _item = item(_item, _parent=self) 

2081 

2082 self._validate_child(_item) 

2083 

2084 if not isinstance(_item, (Whitespace, Comment)): 

2085 if not _item.trivia.indent and len(self._value) > 0 and not self._new: 

2086 _item.trivia.indent = " " 

2087 if _item.trivia.comment: 

2088 _item.trivia.comment = "" 

2089 

2090 self._value.append(key, _item) 

2091 

2092 if isinstance(key, Key): 

2093 key = key.key 

2094 

2095 if key is not None: 

2096 dict.__setitem__(self, key, _item) 

2097 

2098 return self 

2099 

2100 def _validate_child(self, _item: Item) -> None: 

2101 if isinstance(_item, Table): 

2102 raise ValueError("Inline tables cannot contain a table") 

2103 

2104 def as_string(self) -> str: 

2105 buf = "{" 

2106 emitted_key = False 

2107 needs_separator = False 

2108 # Single pass over the body to precompute everything the render loop 

2109 # needs, instead of rescanning the tail on every separator comma (which 

2110 # was O(n^2) on large inline tables): whether any explicit-comma 

2111 # whitespace is present, the index of the last real key, and the index 

2112 # of the last Null (deleted) element. 

2113 has_explicit_commas = False 

2114 last_item_idx = None 

2115 last_null_idx = -1 

2116 for _i, (_k, _v) in enumerate(self._value.body): 

2117 if _k is not None: 

2118 last_item_idx = _i 

2119 elif isinstance(_v, Whitespace) and "," in _v.s: 

2120 has_explicit_commas = True 

2121 if isinstance(_v, Null): 

2122 last_null_idx = _i 

2123 pending_separator = False 

2124 # Buffer position right after the last rendered value, used to place a 

2125 # deferred separator comma after the value rather than after a trailing 

2126 # comment/whitespace. 

2127 last_value_end = len(buf) 

2128 for i, (k, v) in enumerate(self._value.body): 

2129 if k is None: 

2130 if isinstance(v, Whitespace) and "," in v.s: 

2131 if not emitted_key: 

2132 buf += v.as_string().replace(",", "", 1) 

2133 continue 

2134 

2135 # Equivalent to scanning body[i + 1 :] for a Null / a real 

2136 # key, but O(1) using the indices precomputed above. 

2137 has_following_null = last_null_idx > i 

2138 has_following_key = last_item_idx is not None and last_item_idx > i 

2139 if has_following_null and not has_following_key: 

2140 buf += v.as_string().replace(",", "", 1) 

2141 continue 

2142 

2143 if pending_separator: 

2144 # A previous key was removed, leaving this comma as a 

2145 # duplicate of an already emitted separator. Drop it to 

2146 # avoid producing an invalid ``, ,`` sequence. 

2147 buf += v.as_string().replace(",", "", 1) 

2148 continue 

2149 

2150 pending_separator = True 

2151 

2152 if i == len(self._value.body) - 1: 

2153 if self._new: 

2154 buf = buf.rstrip(", ") 

2155 elif not has_explicit_commas or "," in v.as_string(): 

2156 buf = buf.rstrip(",") 

2157 

2158 v_string = v.as_string() 

2159 buf += v_string 

2160 if "," in v_string: 

2161 needs_separator = False 

2162 

2163 continue 

2164 

2165 if has_explicit_commas and needs_separator: 

2166 # Insert the deferred separator right after the previous value, 

2167 # not after any trailing comment/whitespace -- otherwise the 

2168 # comma is swallowed by a trailing comment (see #512). 

2169 # Match the conventional ", " spacing used by the explicit 

2170 # separators already in the table. Only add the space when the 

2171 # text that will follow the comma (the previous value's trailing 

2172 # trivia plus the new key's own indent) does not already start 

2173 # with one, so a padded table like ``{ a = 1 }`` -- which 

2174 # contributes the space itself -- does not end up double-spaced. 

2175 following = buf[last_value_end:] + v.trivia.indent 

2176 separator = "," if following.startswith(" ") else ", " 

2177 buf = f"{buf[:last_value_end]}{separator}{buf[last_value_end:]}" 

2178 needs_separator = False 

2179 

2180 v_trivia_trail = v.trivia.trail.replace("\n", "") 

2181 if k.is_dotted() and isinstance(v, Table): 

2182 # A table materialized from a dotted key renders one 

2183 # `prefix.child = value` pair per child, so that keys added 

2184 # after parsing stay attached to the dotted prefix. 

2185 rendered = ", ".join(self._render_dotted(k, v)) 

2186 else: 

2187 rendered = ( 

2188 f"{k.as_string() + ('.' if k.is_dotted() else '')}" 

2189 f"{k.sep}" 

2190 f"{v.as_string()}" 

2191 ) 

2192 buf += f"{v.trivia.indent}{rendered}" 

2193 last_value_end = len(buf) 

2194 buf += f"{v.trivia.comment}{v_trivia_trail}" 

2195 emitted_key = True 

2196 pending_separator = False 

2197 if has_explicit_commas: 

2198 needs_separator = True 

2199 

2200 if ( 

2201 not has_explicit_commas 

2202 and last_item_idx is not None 

2203 and i < last_item_idx 

2204 ): 

2205 buf += "," 

2206 if self._new: 

2207 buf += " " 

2208 

2209 buf += "}" 

2210 

2211 return buf 

2212 

2213 def _render_dotted(self, key: Key, table: Table) -> list[str]: 

2214 """Render a table materialized from a dotted key as a list of 

2215 ``prefix.child = value`` strings, recursing into nested dotted 

2216 children.""" 

2217 prefix = f"{key.as_string()}.{key.sep}" 

2218 parts = [] 

2219 for k, v in table.value.body: 

2220 if k is None: 

2221 continue 

2222 if isinstance(v, Table): 

2223 parts.extend(f"{prefix}{sub}" for sub in self._render_dotted(k, v)) 

2224 else: 

2225 trail = v.trivia.trail.replace("\n", "") 

2226 parts.append( 

2227 f"{prefix}{v.trivia.indent}{k.as_string()}{k.sep}{v.as_string()}" 

2228 f"{v.trivia.comment_ws}{v.trivia.comment}{trail}" 

2229 ) 

2230 return parts 

2231 

2232 def __setitem__(self, key: Key | str, value: Any) -> None: 

2233 if hasattr(value, "trivia") and value.trivia.comment: 

2234 value.trivia.comment = "" 

2235 if not isinstance(value, Item): 

2236 value = item(value, _parent=self) 

2237 self._validate_child(value) 

2238 super().__setitem__(key, value) 

2239 

2240 def __copy__(self) -> InlineTable: 

2241 return type(self)(self._value.copy(), self._trivia.copy(), self._new) 

2242 

2243 def _getstate(self, protocol: int = 3) -> tuple[container.Container, Trivia]: 

2244 return (self._value, self._trivia) 

2245 

2246 

2247class String(str, Item): 

2248 """ 

2249 A string literal. 

2250 """ 

2251 

2252 def __new__( 

2253 cls, t: StringType, value: str, original: str, trivia: Trivia 

2254 ) -> String: 

2255 return super().__new__(cls, value) 

2256 

2257 def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None: 

2258 super().__init__(trivia) 

2259 

2260 self._t = t 

2261 self._original = original 

2262 

2263 def unwrap(self) -> str: 

2264 return str(self) 

2265 

2266 @property 

2267 def discriminant(self) -> int: 

2268 return 11 

2269 

2270 @property 

2271 def value(self) -> str: 

2272 return self 

2273 

2274 def as_string(self) -> str: 

2275 return f"{self._t.value}{decode(self._original)}{self._t.value}" 

2276 

2277 @property 

2278 def type(self) -> StringType: 

2279 return self._t 

2280 

2281 def __add__(self, other: str) -> String: 

2282 result = super().__add__(other) 

2283 if isinstance(other, String): 

2284 original = self._original + other._original 

2285 else: 

2286 escaped = self._t.escaped_sequences 

2287 original = self._original + ( 

2288 escape_string(other, escaped) if escaped else other 

2289 ) 

2290 

2291 return self._new(result, original) 

2292 

2293 def _new(self, result: str, original: str) -> String: 

2294 return String(self._t, result, original, self._trivia.copy()) 

2295 

2296 def _getstate(self, protocol: int = 3) -> tuple[StringType, str, str, Trivia]: 

2297 return self._t, str(self), self._original, self._trivia 

2298 

2299 @classmethod 

2300 def from_raw( 

2301 cls, value: str, type_: StringType = StringType.SLB, escape: bool = True 

2302 ) -> String: 

2303 value = decode(value) 

2304 

2305 invalid = type_.invalid_sequences 

2306 # collapse CRLF line endings so only a lone CR trips the CR check 

2307 checked = value.replace("\r\n", "\n") 

2308 if any(c in checked for c in invalid): 

2309 raise InvalidStringError(value, invalid, type_.value) 

2310 

2311 string_value = value 

2312 if escape: 

2313 string_value = escape_string(string_value, type_.escaped_sequences) 

2314 if type_ is StringType.MLB and escape: 

2315 string_value = _BARE_CR.sub(r"\\r", string_value) 

2316 if type_.is_multiline() and string_value[:1] in ("\n", "\r"): 

2317 # A newline immediately following the opening delimiter of a 

2318 # multiline string is trimmed by the parser, so a value that 

2319 # starts with a newline would otherwise lose it on round-trip. 

2320 # Emit an extra leading newline (the trimmed one) to preserve it. 

2321 string_value = "\n" + string_value 

2322 

2323 return cls(type_, value, string_value, Trivia()) 

2324 

2325 

2326class AoT(Item, _CustomList): # type: ignore[type-arg] 

2327 """ 

2328 An array of table literal 

2329 """ 

2330 

2331 def __init__( 

2332 self, body: list[Table], name: str | None = None, parsed: bool = False 

2333 ) -> None: 

2334 self.name = name 

2335 self._body: list[Table] = [] 

2336 self._parsed = parsed 

2337 

2338 super().__init__(Trivia(trail="")) 

2339 

2340 for table in body: 

2341 self.append(table) 

2342 

2343 def unwrap(self) -> list[dict[str, Any]]: 

2344 unwrapped = [] 

2345 for t in self._body: 

2346 if hasattr(t, "unwrap"): 

2347 unwrapped.append(t.unwrap()) 

2348 else: 

2349 unwrapped.append(t) 

2350 return unwrapped 

2351 

2352 @property 

2353 def body(self) -> list[Table]: 

2354 return self._body 

2355 

2356 @property 

2357 def discriminant(self) -> int: 

2358 return 12 

2359 

2360 @property 

2361 def value(self) -> list[dict[str, Any]]: 

2362 return [v.value for v in self._body] 

2363 

2364 def __len__(self) -> int: 

2365 return len(self._body) 

2366 

2367 @overload # type: ignore[override] 

2368 def __getitem__(self, key: slice) -> list[Table]: ... 

2369 

2370 @overload 

2371 def __getitem__(self, key: int) -> Table: ... 

2372 

2373 def __getitem__(self, key: int | slice) -> Table | list[Table]: 

2374 return self._body[key] 

2375 

2376 def __setitem__(self, key: slice | int, value: Any) -> None: # type: ignore[override] 

2377 self._body[key] = item(value, _parent=self) 

2378 

2379 def __delitem__(self, key: slice | int) -> None: # type: ignore[override] 

2380 del self._body[key] 

2381 list.__delitem__(self, key) 

2382 

2383 def insert(self, index: int, value: dict[str, Any]) -> None: # type: ignore[override] 

2384 value = item(value, _parent=self) 

2385 if not isinstance(value, Table): 

2386 raise ValueError(f"Unsupported insert value type: {type(value)}") 

2387 length = len(self) 

2388 if index < 0: 

2389 index += length 

2390 if index < 0: 

2391 index = 0 

2392 elif index >= length: 

2393 index = length 

2394 m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) 

2395 if m: 

2396 indent = m.group(1) 

2397 

2398 m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) 

2399 if not m: 

2400 value.trivia.indent = indent 

2401 else: 

2402 value.trivia.indent = m.group(1) + indent + m.group(2) 

2403 prev_table = self._body[index - 1] if 0 < index and length else None 

2404 next_table = self._body[index + 1] if index < length - 1 else None 

2405 if not self._parsed: 

2406 if prev_table and "\n" not in value.trivia.indent: 

2407 value.trivia.indent = "\n" + value.trivia.indent 

2408 if next_table and "\n" not in next_table.trivia.indent: 

2409 next_table.trivia.indent = "\n" + next_table.trivia.indent 

2410 self._body.insert(index, value) 

2411 list.insert(self, index, value) 

2412 

2413 def invalidate_display_name(self) -> None: 

2414 """Call ``invalidate_display_name`` on the contained tables""" 

2415 for child in self: 

2416 if hasattr(child, "invalidate_display_name"): 

2417 child.invalidate_display_name() 

2418 

2419 def as_string(self) -> str: 

2420 b = "" 

2421 for table in self._body: 

2422 b += table.as_string() 

2423 

2424 return b 

2425 

2426 def __repr__(self) -> str: 

2427 return f"<AoT {self.value}>" 

2428 

2429 def _getstate(self, protocol: int = 3) -> tuple[list[Table], str | None, bool]: 

2430 return self._body, self.name, self._parsed 

2431 

2432 

2433class Null(Item): 

2434 """ 

2435 A null item. 

2436 """ 

2437 

2438 def __init__(self) -> None: 

2439 super().__init__(Trivia(trail="")) 

2440 

2441 def unwrap(self) -> None: 

2442 return None 

2443 

2444 @property 

2445 def discriminant(self) -> int: 

2446 return -1 

2447 

2448 @property 

2449 def value(self) -> None: 

2450 return None 

2451 

2452 def as_string(self) -> str: 

2453 return "" 

2454 

2455 def _getstate(self, protocol: int = 3) -> tuple[()]: 

2456 return ()