Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/items.py: 69%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1132 statements  

1from __future__ import annotations 

2 

3import abc 

4import copy 

5import dataclasses 

6import inspect 

7import math 

8import re 

9import string 

10import sys 

11 

12from datetime import date 

13from datetime import datetime 

14from datetime import time 

15from datetime import tzinfo 

16from enum import Enum 

17from typing import TYPE_CHECKING 

18from typing import Any 

19from typing import Collection 

20from typing import Iterable 

21from typing import Iterator 

22from typing import Sequence 

23from typing import TypeVar 

24from typing import cast 

25from typing import overload 

26 

27from tomlkit._compat import PY38 

28from tomlkit._compat import decode 

29from tomlkit._types import _CustomDict 

30from tomlkit._types import _CustomFloat 

31from tomlkit._types import _CustomInt 

32from tomlkit._types import _CustomList 

33from tomlkit._types import wrap_method 

34from tomlkit._utils import CONTROL_CHARS 

35from tomlkit._utils import escape_string 

36from tomlkit.exceptions import ConvertError 

37from tomlkit.exceptions import InvalidStringError 

38 

39 

40if TYPE_CHECKING: 

41 from typing import Protocol 

42 

43 from tomlkit import container 

44 

45 class Encoder(Protocol): 

46 def __call__( 

47 self, __value: Any, _parent: Item | None = None, _sort_keys: bool = False 

48 ) -> Item: ... 

49 

50 

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

52CUSTOM_ENCODERS: list[Encoder] = [] 

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

54 

55 

56@overload 

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

58 

59 

60@overload 

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

62 

63 

64@overload 

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

66 

67 

68@overload 

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

70 

71 

72@overload 

73def item( 

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

75) -> DateTime: ... 

76 

77 

78@overload 

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

80 

81 

82@overload 

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

84 

85 

86@overload 

87def item( 

88 value: Sequence[dict], _parent: Item | None = ..., _sort_keys: bool = ... 

89) -> AoT: ... 

90 

91 

92@overload 

93def item( 

94 value: Sequence, _parent: Item | None = ..., _sort_keys: bool = ... 

95) -> Array: ... 

96 

97 

98@overload 

99def item(value: dict, _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ... 

100 

101 

102@overload 

103def item(value: dict, _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ... 

104 

105 

106@overload 

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

108 

109 

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

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

112 

113 :Example: 

114 

115 >>> item(42) 

116 42 

117 >>> item([1, 2, 3]) 

118 [1, 2, 3] 

119 >>> item({'a': 1, 'b': 2}) 

120 a = 1 

121 b = 2 

122 """ 

123 

124 from tomlkit.container import Container 

125 

126 if isinstance(value, Item): 

127 return value 

128 

129 if isinstance(value, bool): 

130 return Bool(value, Trivia()) 

131 elif isinstance(value, int): 

132 return Integer(value, Trivia(), str(value)) 

133 elif isinstance(value, float): 

134 return Float(value, Trivia(), str(value)) 

135 elif isinstance(value, dict): 

136 table_constructor = ( 

137 InlineTable if isinstance(_parent, (Array, InlineTable)) else Table 

138 ) 

139 val = table_constructor(Container(), Trivia(), False) 

140 for k, v in sorted( 

141 value.items(), 

142 key=lambda i: (isinstance(i[1], dict), i[0]) if _sort_keys else 1, 

143 ): 

144 val[k] = item(v, _parent=val, _sort_keys=_sort_keys) 

145 

146 return val 

147 elif isinstance(value, (list, tuple)): 

148 if ( 

149 value 

150 and all(isinstance(v, dict) for v in value) 

151 and (_parent is None or isinstance(_parent, Table)) 

152 ): 

153 a = AoT([]) 

154 table_constructor = Table 

155 else: 

156 a = Array([], Trivia()) 

157 table_constructor = InlineTable 

158 

159 for v in value: 

160 if isinstance(v, dict): 

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

162 

163 for k, _v in sorted( 

164 v.items(), 

165 key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), 

166 ): 

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

168 if isinstance(table, InlineTable): 

169 i.trivia.trail = "" 

170 

171 table[k] = i 

172 

173 v = table 

174 

175 a.append(v) 

176 

177 return a 

178 elif isinstance(value, str): 

179 return String.from_raw(value) 

180 elif isinstance(value, datetime): 

181 return DateTime( 

182 value.year, 

183 value.month, 

184 value.day, 

185 value.hour, 

186 value.minute, 

187 value.second, 

188 value.microsecond, 

189 value.tzinfo, 

190 Trivia(), 

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

192 ) 

193 elif isinstance(value, date): 

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

195 elif isinstance(value, time): 

196 return Time( 

197 value.hour, 

198 value.minute, 

199 value.second, 

200 value.microsecond, 

201 value.tzinfo, 

202 Trivia(), 

203 value.isoformat(), 

204 ) 

205 else: 

206 for encoder in CUSTOM_ENCODERS: 

207 try: 

208 # Check if encoder accepts keyword arguments for backward compatibility 

209 sig = inspect.signature(encoder) 

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

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

212 ): 

213 # New style encoder that can accept additional parameters 

214 rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys) 

215 else: 

216 # Old style encoder that only accepts value 

217 rv = encoder(value) 

218 except ConvertError: 

219 pass 

220 else: 

221 if not isinstance(rv, Item): 

222 raise ConvertError( 

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

224 ) 

225 return rv 

226 

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

228 

229 

230class StringType(Enum): 

231 # Single Line Basic 

232 SLB = '"' 

233 # Multi Line Basic 

234 MLB = '"""' 

235 # Single Line Literal 

236 SLL = "'" 

237 # Multi Line Literal 

238 MLL = "'''" 

239 

240 @classmethod 

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

242 return { 

243 (False, False): cls.SLB, 

244 (False, True): cls.MLB, 

245 (True, False): cls.SLL, 

246 (True, True): cls.MLL, 

247 }[(literal, multiline)] 

248 

249 @property 

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

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

252 escaped_in_basic = CONTROL_CHARS | {"\\"} 

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

254 return { 

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

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

257 StringType.SLL: (), 

258 StringType.MLL: (), 

259 }[self] 

260 

261 @property 

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

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

264 forbidden_in_literal = CONTROL_CHARS - {"\t"} 

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

266 return { 

267 StringType.SLB: (), 

268 StringType.MLB: (), 

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

270 StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline, 

271 }[self] 

272 

273 @property 

274 def unit(self) -> str: 

275 return self.value[0] 

276 

277 def is_basic(self) -> bool: 

278 return self in {StringType.SLB, StringType.MLB} 

279 

280 def is_literal(self) -> bool: 

281 return self in {StringType.SLL, StringType.MLL} 

282 

283 def is_singleline(self) -> bool: 

284 return self in {StringType.SLB, StringType.SLL} 

285 

286 def is_multiline(self) -> bool: 

287 return self in {StringType.MLB, StringType.MLL} 

288 

289 def toggle(self) -> StringType: 

290 return { 

291 StringType.SLB: StringType.MLB, 

292 StringType.MLB: StringType.SLB, 

293 StringType.SLL: StringType.MLL, 

294 StringType.MLL: StringType.SLL, 

295 }[self] 

296 

297 

298class BoolType(Enum): 

299 TRUE = "true" 

300 FALSE = "false" 

301 

302 def __bool__(self): 

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

304 

305 def __iter__(self): 

306 return iter(self.value) 

307 

308 def __len__(self): 

309 return len(self.value) 

310 

311 

312@dataclasses.dataclass 

313class Trivia: 

314 """ 

315 Trivia information (aka metadata). 

316 """ 

317 

318 # Whitespace before a value. 

319 indent: str = "" 

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

321 comment_ws: str = "" 

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

323 comment: str = "" 

324 # Trailing newline. 

325 trail: str = "\n" 

326 

327 def copy(self) -> Trivia: 

328 return dataclasses.replace(self) 

329 

330 

331class KeyType(Enum): 

332 """ 

333 The type of a Key. 

334 

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

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

337 """ 

338 

339 Bare = "" 

340 Basic = '"' 

341 Literal = "'" 

342 

343 

344class Key(abc.ABC): 

345 """Base class for a key""" 

346 

347 sep: str 

348 _original: str 

349 _keys: list[SingleKey] 

350 _dotted: bool 

351 key: str 

352 

353 @abc.abstractmethod 

354 def __hash__(self) -> int: 

355 pass 

356 

357 @abc.abstractmethod 

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

359 pass 

360 

361 def is_dotted(self) -> bool: 

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

363 return self._dotted 

364 

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

366 return iter(self._keys) 

367 

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

369 """Concatenate keys into a dotted key""" 

370 keys = self._keys + other._keys 

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

372 

373 def is_multi(self) -> bool: 

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

375 return len(self._keys) > 1 

376 

377 def as_string(self) -> str: 

378 """The TOML representation""" 

379 return self._original 

380 

381 def __str__(self) -> str: 

382 return self.as_string() 

383 

384 def __repr__(self) -> str: 

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

386 

387 

388class SingleKey(Key): 

389 """A single key""" 

390 

391 def __init__( 

392 self, 

393 k: str, 

394 t: KeyType | None = None, 

395 sep: str | None = None, 

396 original: str | None = None, 

397 ) -> None: 

398 if not isinstance(k, str): 

399 raise TypeError("Keys must be strings") 

400 

401 if t is None: 

402 if not k or any( 

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

404 ): 

405 t = KeyType.Basic 

406 else: 

407 t = KeyType.Bare 

408 

409 self.t = t 

410 if sep is None: 

411 sep = " = " 

412 

413 self.sep = sep 

414 self.key = k 

415 if original is None: 

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

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

418 

419 self._original = original 

420 self._keys = [self] 

421 self._dotted = False 

422 

423 @property 

424 def delimiter(self) -> str: 

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

426 return self.t.value 

427 

428 def is_bare(self) -> bool: 

429 """Check if the key is bare""" 

430 return self.t == KeyType.Bare 

431 

432 def __hash__(self) -> int: 

433 return hash(self.key) 

434 

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

436 if isinstance(other, Key): 

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

438 

439 return self.key == other 

440 

441 

442class DottedKey(Key): 

443 def __init__( 

444 self, 

445 keys: Iterable[SingleKey], 

446 sep: str | None = None, 

447 original: str | None = None, 

448 ) -> None: 

449 self._keys = list(keys) 

450 if original is None: 

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

452 

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

454 self._original = original 

455 self._dotted = False 

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

457 

458 def __hash__(self) -> int: 

459 return hash(tuple(self._keys)) 

460 

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

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

463 

464 

465class Item: 

466 """ 

467 An item within a TOML document. 

468 """ 

469 

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

471 self._trivia = trivia 

472 

473 @property 

474 def trivia(self) -> Trivia: 

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

476 return self._trivia 

477 

478 @property 

479 def discriminant(self) -> int: 

480 raise NotImplementedError() 

481 

482 def as_string(self) -> str: 

483 """The TOML representation""" 

484 raise NotImplementedError() 

485 

486 @property 

487 def value(self) -> Any: 

488 return self 

489 

490 def unwrap(self) -> Any: 

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

492 raise NotImplementedError() 

493 

494 # Helpers 

495 

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

497 """Attach a comment to this item""" 

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

499 comment = "# " + comment 

500 

501 self._trivia.comment_ws = " " 

502 self._trivia.comment = comment 

503 

504 return self 

505 

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

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

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

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

510 else: 

511 self._trivia.indent = " " * indent 

512 

513 return self 

514 

515 def is_boolean(self) -> bool: 

516 return isinstance(self, Bool) 

517 

518 def is_table(self) -> bool: 

519 return isinstance(self, Table) 

520 

521 def is_inline_table(self) -> bool: 

522 return isinstance(self, InlineTable) 

523 

524 def is_aot(self) -> bool: 

525 return isinstance(self, AoT) 

526 

527 def _getstate(self, protocol=3): 

528 return (self._trivia,) 

529 

530 def __reduce__(self): 

531 return self.__reduce_ex__(2) 

532 

533 def __reduce_ex__(self, protocol): 

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

535 

536 

537class Whitespace(Item): 

538 """ 

539 A whitespace literal. 

540 """ 

541 

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

543 self._s = s 

544 self._fixed = fixed 

545 

546 @property 

547 def s(self) -> str: 

548 return self._s 

549 

550 @property 

551 def value(self) -> str: 

552 """The wrapped string of the whitespace""" 

553 return self._s 

554 

555 @property 

556 def trivia(self) -> Trivia: 

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

558 

559 @property 

560 def discriminant(self) -> int: 

561 return 0 

562 

563 def is_fixed(self) -> bool: 

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

565 return self._fixed 

566 

567 def as_string(self) -> str: 

568 return self._s 

569 

570 def __repr__(self) -> str: 

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

572 

573 def _getstate(self, protocol=3): 

574 return self._s, self._fixed 

575 

576 

577class Comment(Item): 

578 """ 

579 A comment literal. 

580 """ 

581 

582 @property 

583 def discriminant(self) -> int: 

584 return 1 

585 

586 def as_string(self) -> str: 

587 return ( 

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

589 ) 

590 

591 def __str__(self) -> str: 

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

593 

594 

595class Integer(Item, _CustomInt): 

596 """ 

597 An integer literal. 

598 """ 

599 

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

601 return int.__new__(cls, value) 

602 

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

604 super().__init__(trivia) 

605 self._original = value 

606 self._raw = raw 

607 self._sign = False 

608 

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

610 self._sign = True 

611 

612 def unwrap(self) -> int: 

613 return self._original 

614 

615 __int__ = unwrap 

616 

617 def __hash__(self) -> int: 

618 return hash(self.unwrap()) 

619 

620 @property 

621 def discriminant(self) -> int: 

622 return 2 

623 

624 @property 

625 def value(self) -> int: 

626 """The wrapped integer value""" 

627 return self 

628 

629 def as_string(self) -> str: 

630 return self._raw 

631 

632 def _new(self, result): 

633 raw = str(result) 

634 if self._sign and result >= 0: 

635 raw = f"+{raw}" 

636 

637 return Integer(result, self._trivia, raw) 

638 

639 def _getstate(self, protocol=3): 

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

641 

642 # int methods 

643 __abs__ = wrap_method(int.__abs__) 

644 __add__ = wrap_method(int.__add__) 

645 __and__ = wrap_method(int.__and__) 

646 __ceil__ = wrap_method(int.__ceil__) 

647 __eq__ = int.__eq__ 

648 __floor__ = wrap_method(int.__floor__) 

649 __floordiv__ = wrap_method(int.__floordiv__) 

650 __invert__ = wrap_method(int.__invert__) 

651 __le__ = int.__le__ 

652 __lshift__ = wrap_method(int.__lshift__) 

653 __lt__ = int.__lt__ 

654 __mod__ = wrap_method(int.__mod__) 

655 __mul__ = wrap_method(int.__mul__) 

656 __neg__ = wrap_method(int.__neg__) 

657 __or__ = wrap_method(int.__or__) 

658 __pos__ = wrap_method(int.__pos__) 

659 __pow__ = wrap_method(int.__pow__) 

660 __radd__ = wrap_method(int.__radd__) 

661 __rand__ = wrap_method(int.__rand__) 

662 __rfloordiv__ = wrap_method(int.__rfloordiv__) 

663 __rlshift__ = wrap_method(int.__rlshift__) 

664 __rmod__ = wrap_method(int.__rmod__) 

665 __rmul__ = wrap_method(int.__rmul__) 

666 __ror__ = wrap_method(int.__ror__) 

667 __round__ = wrap_method(int.__round__) 

668 __rpow__ = wrap_method(int.__rpow__) 

669 __rrshift__ = wrap_method(int.__rrshift__) 

670 __rshift__ = wrap_method(int.__rshift__) 

671 __rxor__ = wrap_method(int.__rxor__) 

672 __trunc__ = wrap_method(int.__trunc__) 

673 __xor__ = wrap_method(int.__xor__) 

674 

675 def __rtruediv__(self, other): 

676 result = int.__rtruediv__(self, other) 

677 if result is NotImplemented: 

678 return result 

679 return Float._new(self, result) 

680 

681 def __truediv__(self, other): 

682 result = int.__truediv__(self, other) 

683 if result is NotImplemented: 

684 return result 

685 return Float._new(self, result) 

686 

687 

688class Float(Item, _CustomFloat): 

689 """ 

690 A float literal. 

691 """ 

692 

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

694 return float.__new__(cls, value) 

695 

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

697 super().__init__(trivia) 

698 self._original = value 

699 self._raw = raw 

700 self._sign = False 

701 

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

703 self._sign = True 

704 

705 def unwrap(self) -> float: 

706 return self._original 

707 

708 __float__ = unwrap 

709 

710 def __hash__(self) -> int: 

711 return hash(self.unwrap()) 

712 

713 @property 

714 def discriminant(self) -> int: 

715 return 3 

716 

717 @property 

718 def value(self) -> float: 

719 """The wrapped float value""" 

720 return self 

721 

722 def as_string(self) -> str: 

723 return self._raw 

724 

725 def _new(self, result): 

726 raw = str(result) 

727 

728 if self._sign and result >= 0: 

729 raw = f"+{raw}" 

730 

731 return Float(result, self._trivia, raw) 

732 

733 def _getstate(self, protocol=3): 

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

735 

736 # float methods 

737 __abs__ = wrap_method(float.__abs__) 

738 __add__ = wrap_method(float.__add__) 

739 __eq__ = float.__eq__ 

740 __floordiv__ = wrap_method(float.__floordiv__) 

741 __le__ = float.__le__ 

742 __lt__ = float.__lt__ 

743 __mod__ = wrap_method(float.__mod__) 

744 __mul__ = wrap_method(float.__mul__) 

745 __neg__ = wrap_method(float.__neg__) 

746 __pos__ = wrap_method(float.__pos__) 

747 __pow__ = wrap_method(float.__pow__) 

748 __radd__ = wrap_method(float.__radd__) 

749 __rfloordiv__ = wrap_method(float.__rfloordiv__) 

750 __rmod__ = wrap_method(float.__rmod__) 

751 __rmul__ = wrap_method(float.__rmul__) 

752 __round__ = wrap_method(float.__round__) 

753 __rpow__ = wrap_method(float.__rpow__) 

754 __rtruediv__ = wrap_method(float.__rtruediv__) 

755 __truediv__ = wrap_method(float.__truediv__) 

756 __trunc__ = float.__trunc__ 

757 

758 if sys.version_info >= (3, 9): 

759 __ceil__ = float.__ceil__ 

760 __floor__ = float.__floor__ 

761 else: 

762 __ceil__ = math.ceil 

763 __floor__ = math.floor 

764 

765 

766class Bool(Item): 

767 """ 

768 A boolean literal. 

769 """ 

770 

771 def __init__(self, t: int, trivia: Trivia) -> None: 

772 super().__init__(trivia) 

773 

774 self._value = bool(t) 

775 

776 def unwrap(self) -> bool: 

777 return bool(self) 

778 

779 @property 

780 def discriminant(self) -> int: 

781 return 4 

782 

783 @property 

784 def value(self) -> bool: 

785 """The wrapped boolean value""" 

786 return self._value 

787 

788 def as_string(self) -> str: 

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

790 

791 def _getstate(self, protocol=3): 

792 return self._value, self._trivia 

793 

794 def __bool__(self): 

795 return self._value 

796 

797 __nonzero__ = __bool__ 

798 

799 def __eq__(self, other): 

800 if not isinstance(other, bool): 

801 return NotImplemented 

802 

803 return other == self._value 

804 

805 def __hash__(self): 

806 return hash(self._value) 

807 

808 def __repr__(self): 

809 return repr(self._value) 

810 

811 

812class DateTime(Item, datetime): 

813 """ 

814 A datetime literal. 

815 """ 

816 

817 def __new__( 

818 cls, 

819 year: int, 

820 month: int, 

821 day: int, 

822 hour: int, 

823 minute: int, 

824 second: int, 

825 microsecond: int, 

826 tzinfo: tzinfo | None, 

827 *_: Any, 

828 **kwargs: Any, 

829 ) -> datetime: 

830 return datetime.__new__( 

831 cls, 

832 year, 

833 month, 

834 day, 

835 hour, 

836 minute, 

837 second, 

838 microsecond, 

839 tzinfo=tzinfo, 

840 **kwargs, 

841 ) 

842 

843 def __init__( 

844 self, 

845 year: int, 

846 month: int, 

847 day: int, 

848 hour: int, 

849 minute: int, 

850 second: int, 

851 microsecond: int, 

852 tzinfo: tzinfo | None, 

853 trivia: Trivia | None = None, 

854 raw: str | None = None, 

855 **kwargs: Any, 

856 ) -> None: 

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

858 

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

860 

861 def unwrap(self) -> datetime: 

862 ( 

863 year, 

864 month, 

865 day, 

866 hour, 

867 minute, 

868 second, 

869 microsecond, 

870 tzinfo, 

871 _, 

872 _, 

873 ) = self._getstate() 

874 return datetime(year, month, day, hour, minute, second, microsecond, tzinfo) 

875 

876 @property 

877 def discriminant(self) -> int: 

878 return 5 

879 

880 @property 

881 def value(self) -> datetime: 

882 return self 

883 

884 def as_string(self) -> str: 

885 return self._raw 

886 

887 def __add__(self, other): 

888 if PY38: 

889 result = datetime( 

890 self.year, 

891 self.month, 

892 self.day, 

893 self.hour, 

894 self.minute, 

895 self.second, 

896 self.microsecond, 

897 self.tzinfo, 

898 ).__add__(other) 

899 else: 

900 result = super().__add__(other) 

901 

902 return self._new(result) 

903 

904 def __sub__(self, other): 

905 if PY38: 

906 result = datetime( 

907 self.year, 

908 self.month, 

909 self.day, 

910 self.hour, 

911 self.minute, 

912 self.second, 

913 self.microsecond, 

914 self.tzinfo, 

915 ).__sub__(other) 

916 else: 

917 result = super().__sub__(other) 

918 

919 if isinstance(result, datetime): 

920 result = self._new(result) 

921 

922 return result 

923 

924 def replace(self, *args: Any, **kwargs: Any) -> datetime: 

925 return self._new(super().replace(*args, **kwargs)) 

926 

927 def astimezone(self, tz: tzinfo) -> datetime: 

928 result = super().astimezone(tz) 

929 if PY38: 

930 return result 

931 return self._new(result) 

932 

933 def _new(self, result) -> DateTime: 

934 raw = result.isoformat() 

935 

936 return DateTime( 

937 result.year, 

938 result.month, 

939 result.day, 

940 result.hour, 

941 result.minute, 

942 result.second, 

943 result.microsecond, 

944 result.tzinfo, 

945 self._trivia, 

946 raw, 

947 ) 

948 

949 def _getstate(self, protocol=3): 

950 return ( 

951 self.year, 

952 self.month, 

953 self.day, 

954 self.hour, 

955 self.minute, 

956 self.second, 

957 self.microsecond, 

958 self.tzinfo, 

959 self._trivia, 

960 self._raw, 

961 ) 

962 

963 

964class Date(Item, date): 

965 """ 

966 A date literal. 

967 """ 

968 

969 def __new__(cls, year: int, month: int, day: int, *_: Any) -> date: 

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

971 

972 def __init__( 

973 self, 

974 year: int, 

975 month: int, 

976 day: int, 

977 trivia: Trivia | None = None, 

978 raw: str = "", 

979 ) -> None: 

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

981 

982 self._raw = raw 

983 

984 def unwrap(self) -> date: 

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

986 return date(year, month, day) 

987 

988 @property 

989 def discriminant(self) -> int: 

990 return 6 

991 

992 @property 

993 def value(self) -> date: 

994 return self 

995 

996 def as_string(self) -> str: 

997 return self._raw 

998 

999 def __add__(self, other): 

1000 if PY38: 

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

1002 else: 

1003 result = super().__add__(other) 

1004 

1005 return self._new(result) 

1006 

1007 def __sub__(self, other): 

1008 if PY38: 

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

1010 else: 

1011 result = super().__sub__(other) 

1012 

1013 if isinstance(result, date): 

1014 result = self._new(result) 

1015 

1016 return result 

1017 

1018 def replace(self, *args: Any, **kwargs: Any) -> date: 

1019 return self._new(super().replace(*args, **kwargs)) 

1020 

1021 def _new(self, result): 

1022 raw = result.isoformat() 

1023 

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

1025 

1026 def _getstate(self, protocol=3): 

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

1028 

1029 

1030class Time(Item, time): 

1031 """ 

1032 A time literal. 

1033 """ 

1034 

1035 def __new__( 

1036 cls, 

1037 hour: int, 

1038 minute: int, 

1039 second: int, 

1040 microsecond: int, 

1041 tzinfo: tzinfo | None, 

1042 *_: Any, 

1043 ) -> time: 

1044 return time.__new__(cls, hour, minute, second, microsecond, tzinfo) 

1045 

1046 def __init__( 

1047 self, 

1048 hour: int, 

1049 minute: int, 

1050 second: int, 

1051 microsecond: int, 

1052 tzinfo: tzinfo | None, 

1053 trivia: Trivia | None = None, 

1054 raw: str = "", 

1055 ) -> None: 

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

1057 

1058 self._raw = raw 

1059 

1060 def unwrap(self) -> time: 

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

1062 return time(hour, minute, second, microsecond, tzinfo) 

1063 

1064 @property 

1065 def discriminant(self) -> int: 

1066 return 7 

1067 

1068 @property 

1069 def value(self) -> time: 

1070 return self 

1071 

1072 def as_string(self) -> str: 

1073 return self._raw 

1074 

1075 def replace(self, *args: Any, **kwargs: Any) -> time: 

1076 return self._new(super().replace(*args, **kwargs)) 

1077 

1078 def _new(self, result): 

1079 raw = result.isoformat() 

1080 

1081 return Time( 

1082 result.hour, 

1083 result.minute, 

1084 result.second, 

1085 result.microsecond, 

1086 result.tzinfo, 

1087 self._trivia, 

1088 raw, 

1089 ) 

1090 

1091 def _getstate(self, protocol: int = 3) -> tuple: 

1092 return ( 

1093 self.hour, 

1094 self.minute, 

1095 self.second, 

1096 self.microsecond, 

1097 self.tzinfo, 

1098 self._trivia, 

1099 self._raw, 

1100 ) 

1101 

1102 

1103class _ArrayItemGroup: 

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

1105 

1106 def __init__( 

1107 self, 

1108 value: Item | None = None, 

1109 indent: Whitespace | None = None, 

1110 comma: Whitespace | None = None, 

1111 comment: Comment | None = None, 

1112 ) -> None: 

1113 self.value = value 

1114 self.indent = indent 

1115 self.comma = comma 

1116 self.comment = comment 

1117 

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

1119 return filter( 

1120 lambda x: x is not None, (self.indent, self.value, self.comma, self.comment) 

1121 ) 

1122 

1123 def __repr__(self) -> str: 

1124 return repr(tuple(self)) 

1125 

1126 def is_whitespace(self) -> bool: 

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

1128 

1129 def __bool__(self) -> bool: 

1130 try: 

1131 next(iter(self)) 

1132 except StopIteration: 

1133 return False 

1134 return True 

1135 

1136 

1137class Array(Item, _CustomList): 

1138 """ 

1139 An array literal 

1140 """ 

1141 

1142 def __init__( 

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

1144 ) -> None: 

1145 super().__init__(trivia) 

1146 list.__init__( 

1147 self, 

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

1149 ) 

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

1151 self._value = self._group_values(value) 

1152 self._multiline = multiline 

1153 self._reindex() 

1154 

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

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

1157 groups = [] 

1158 this_group = _ArrayItemGroup() 

1159 start_new_group = False 

1160 for item in value: 

1161 if isinstance(item, Whitespace): 

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

1163 groups.append(this_group) 

1164 this_group = _ArrayItemGroup(indent=item) 

1165 start_new_group = False 

1166 else: 

1167 if this_group.value is None: 

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

1169 this_group.value = Null() 

1170 this_group.comma = item 

1171 elif isinstance(item, Comment): 

1172 if this_group.value is None: 

1173 this_group.value = Null() 

1174 this_group.comment = item 

1175 # Comments are the last item in a group. 

1176 start_new_group = True 

1177 elif this_group.value is None: 

1178 this_group.value = item 

1179 else: 

1180 groups.append(this_group) 

1181 this_group = _ArrayItemGroup(value=item) 

1182 groups.append(this_group) 

1183 return [group for group in groups if group] 

1184 

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

1186 unwrapped = [] 

1187 for v in self: 

1188 if hasattr(v, "unwrap"): 

1189 unwrapped.append(v.unwrap()) 

1190 else: 

1191 unwrapped.append(v) 

1192 return unwrapped 

1193 

1194 @property 

1195 def discriminant(self) -> int: 

1196 return 8 

1197 

1198 @property 

1199 def value(self) -> list: 

1200 return self 

1201 

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

1203 for v in self._value: 

1204 yield from v 

1205 

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

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

1208 

1209 :Example: 

1210 

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

1212 >>> print(a.as_string()) 

1213 [1, 2, 3] 

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

1215 [ 

1216 1, 

1217 2, 

1218 3, 

1219 ] 

1220 """ 

1221 self._multiline = multiline 

1222 

1223 return self 

1224 

1225 def as_string(self) -> str: 

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

1227 return f"[{''.join(v.as_string() for v in self._iter_items())}]" 

1228 

1229 s = "[\n" 

1230 s += "".join( 

1231 self.trivia.indent 

1232 + " " * 4 

1233 + v.value.as_string() 

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

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

1236 + "\n" 

1237 for v in self._value 

1238 if v.value is not None 

1239 ) 

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

1241 

1242 return s 

1243 

1244 def _reindex(self) -> None: 

1245 self._index_map.clear() 

1246 index = 0 

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

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

1249 continue 

1250 self._index_map[index] = i 

1251 index += 1 

1252 

1253 def add_line( 

1254 self, 

1255 *items: Any, 

1256 indent: str = " ", 

1257 comment: str | None = None, 

1258 add_comma: bool = True, 

1259 newline: bool = True, 

1260 ) -> None: 

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

1262 When add_comma is True, only accept actual values and 

1263 ", " will be added between values automatically. 

1264 

1265 :Example: 

1266 

1267 >>> a = array() 

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

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

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

1271 >>> print(a.as_string()) 

1272 [ 

1273 1, 2, 3, 

1274 4, 5, 6, 

1275 ] 

1276 """ 

1277 new_values: list[Item] = [] 

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

1279 if first_indent: 

1280 new_values.append(Whitespace(first_indent)) 

1281 whitespace = "" 

1282 data_values = [] 

1283 for i, el in enumerate(items): 

1284 it = item(el, _parent=self) 

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

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

1287 if not isinstance(it, Whitespace): 

1288 if whitespace: 

1289 new_values.append(Whitespace(whitespace)) 

1290 whitespace = "" 

1291 new_values.append(it) 

1292 data_values.append(it.value) 

1293 if add_comma: 

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

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

1296 new_values.append(Whitespace(" ")) 

1297 elif "," not in it.s: 

1298 whitespace += it.s 

1299 else: 

1300 new_values.append(it) 

1301 if whitespace: 

1302 new_values.append(Whitespace(whitespace)) 

1303 if comment: 

1304 indent = " " if items else "" 

1305 new_values.append( 

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

1307 ) 

1308 list.extend(self, data_values) 

1309 if len(self._value) > 0: 

1310 last_item = self._value[-1] 

1311 last_value_item = next( 

1312 ( 

1313 v 

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

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

1316 ), 

1317 None, 

1318 ) 

1319 if last_value_item is not None: 

1320 last_value_item.comma = Whitespace(",") 

1321 if last_item.is_whitespace(): 

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

1323 else: 

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

1325 else: 

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

1327 self._reindex() 

1328 

1329 def clear(self) -> None: 

1330 """Clear the array.""" 

1331 list.clear(self) 

1332 self._index_map.clear() 

1333 self._value.clear() 

1334 

1335 def __len__(self) -> int: 

1336 return list.__len__(self) 

1337 

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

1339 rv = list.__getitem__(self, index) 

1340 return cast(Item, rv) 

1341 

1342 def __getitem__(self, key: int | slice) -> Any: 

1343 rv = list.__getitem__(self, key) 

1344 if isinstance(rv, Bool): 

1345 return rv.value 

1346 return rv 

1347 

1348 def __setitem__(self, key: int | slice, value: Any) -> Any: 

1349 it = item(value, _parent=self) 

1350 list.__setitem__(self, key, it) 

1351 if isinstance(key, slice): 

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

1353 if key < 0: 

1354 key += len(self) 

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

1356 

1357 def insert(self, pos: int, value: Any) -> None: 

1358 it = item(value, _parent=self) 

1359 length = len(self) 

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

1361 list.insert(self, pos, it) 

1362 if pos < 0: 

1363 pos += length 

1364 if pos < 0: 

1365 pos = 0 

1366 

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

1368 default_indent = " " 

1369 if pos < length: 

1370 try: 

1371 idx = self._index_map[pos] 

1372 except KeyError as e: 

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

1374 else: 

1375 idx = len(self._value) 

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

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

1378 idx -= 1 

1379 if ( 

1380 self._value[idx].indent is not None 

1381 and "\n" in self._value[idx].indent.s 

1382 ): 

1383 default_indent = "\n " 

1384 indent: Item | None = None 

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

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

1387 # Prefer to copy the indentation from the item after 

1388 indent = self._value[idx].indent 

1389 if idx > 0: 

1390 last_item = self._value[idx - 1] 

1391 if indent is None: 

1392 indent = last_item.indent 

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

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

1395 # 2) the array is multiline 

1396 comma = last_item.comma 

1397 if last_item.comma is None and not isinstance(last_item.value, Null): 

1398 # Add comma to the last item to separate it from the following items. 

1399 last_item.comma = Whitespace(",") 

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

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

1402 indent = Whitespace(default_indent) 

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

1404 self._value.insert(idx, new_item) 

1405 self._reindex() 

1406 

1407 def __delitem__(self, key: int | slice): 

1408 length = len(self) 

1409 list.__delitem__(self, key) 

1410 

1411 if isinstance(key, slice): 

1412 indices_to_remove = list( 

1413 range(key.start or 0, key.stop or length, key.step or 1) 

1414 ) 

1415 else: 

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

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

1418 try: 

1419 idx = self._index_map[i] 

1420 except KeyError as e: 

1421 if not isinstance(key, slice): 

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

1423 else: 

1424 group_rm = self._value[idx] 

1425 del self._value[idx] 

1426 if ( 

1427 idx == 0 

1428 and len(self._value) > 0 

1429 and self._value[idx].indent 

1430 and "\n" not in self._value[idx].indent.s 

1431 ): 

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

1433 self._value[idx].indent = None 

1434 comma_in_indent = ( 

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

1436 ) 

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

1438 if comma_in_indent and comma_in_comma: 

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

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

1441 if group is not None: 

1442 if group.indent is None: 

1443 group.indent = Whitespace(",") 

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

1445 # Insert the comma after the newline 

1446 try: 

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

1448 group.indent._s = ( 

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

1450 + "," 

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

1452 ) 

1453 except ValueError: 

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

1455 elif not comma_in_indent and not comma_in_comma: 

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

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

1458 group = self._value[j] 

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

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

1461 break 

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

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

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

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

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

1467 if group is not None and ( 

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

1469 ): 

1470 group.indent = group_rm.indent 

1471 

1472 if len(self._value) > 0: 

1473 v = self._value[-1] 

1474 if not v.is_whitespace(): 

1475 # remove the comma of the last item 

1476 v.comma = None 

1477 

1478 self._reindex() 

1479 

1480 def _getstate(self, protocol=3): 

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

1482 

1483 

1484class AbstractTable(Item, _CustomDict): 

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

1486 

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

1488 Item.__init__(self, trivia) 

1489 

1490 self._value = value 

1491 

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

1493 if k is not None: 

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

1495 

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

1497 unwrapped = {} 

1498 for k, v in self.items(): 

1499 if isinstance(k, Key): 

1500 k = k.key 

1501 if hasattr(v, "unwrap"): 

1502 v = v.unwrap() 

1503 unwrapped[k] = v 

1504 

1505 return unwrapped 

1506 

1507 @property 

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

1509 return self._value 

1510 

1511 @overload 

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

1513 

1514 @overload 

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

1516 

1517 def append(self, key, value): 

1518 raise NotImplementedError 

1519 

1520 @overload 

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

1522 

1523 @overload 

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

1525 

1526 def add(self, key, value=None): 

1527 if value is None: 

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

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

1530 raise ValueError(msg) 

1531 

1532 key, value = None, key 

1533 

1534 return self.append(key, value) 

1535 

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

1537 self._value.remove(key) 

1538 

1539 if isinstance(key, Key): 

1540 key = key.key 

1541 

1542 if key is not None: 

1543 dict.__delitem__(self, key) 

1544 

1545 return self 

1546 

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

1548 return self._value.item(key) 

1549 

1550 def setdefault(self, key: Key | str, default: Any) -> Any: 

1551 super().setdefault(key, default) 

1552 return self[key] 

1553 

1554 def __str__(self): 

1555 return str(self.value) 

1556 

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

1558 return copy.copy(self) 

1559 

1560 def __repr__(self) -> str: 

1561 return repr(self.value) 

1562 

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

1564 return iter(self._value) 

1565 

1566 def __len__(self) -> int: 

1567 return len(self._value) 

1568 

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

1570 self.remove(key) 

1571 

1572 def __getitem__(self, key: Key | str) -> Item: 

1573 return cast(Item, self._value[key]) 

1574 

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

1576 if not isinstance(value, Item): 

1577 value = item(value, _parent=self) 

1578 

1579 is_replace = key in self 

1580 self._value[key] = value 

1581 

1582 if key is not None: 

1583 dict.__setitem__(self, key, value) 

1584 

1585 if is_replace: 

1586 return 

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

1588 if not m: 

1589 return 

1590 

1591 indent = m.group(1) 

1592 

1593 if not isinstance(value, Whitespace): 

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

1595 if not m: 

1596 value.trivia.indent = indent 

1597 else: 

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

1599 

1600 

1601class Table(AbstractTable): 

1602 """ 

1603 A table literal. 

1604 """ 

1605 

1606 def __init__( 

1607 self, 

1608 value: container.Container, 

1609 trivia: Trivia, 

1610 is_aot_element: bool, 

1611 is_super_table: bool | None = None, 

1612 name: str | None = None, 

1613 display_name: str | None = None, 

1614 ) -> None: 

1615 super().__init__(value, trivia) 

1616 

1617 self.name = name 

1618 self.display_name = display_name 

1619 self._is_aot_element = is_aot_element 

1620 self._is_super_table = is_super_table 

1621 

1622 @property 

1623 def discriminant(self) -> int: 

1624 return 9 

1625 

1626 def __copy__(self) -> Table: 

1627 return type(self)( 

1628 self._value.copy(), 

1629 self._trivia.copy(), 

1630 self._is_aot_element, 

1631 self._is_super_table, 

1632 self.name, 

1633 self.display_name, 

1634 ) 

1635 

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

1637 """ 

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

1639 """ 

1640 if not isinstance(_item, Item): 

1641 _item = item(_item, _parent=self) 

1642 

1643 self._value.append(key, _item) 

1644 

1645 if isinstance(key, Key): 

1646 key = next(iter(key)).key 

1647 _item = self._value[key] 

1648 

1649 if key is not None: 

1650 dict.__setitem__(self, key, _item) 

1651 

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

1653 if not m: 

1654 return self 

1655 

1656 indent = m.group(1) 

1657 

1658 if not isinstance(_item, Whitespace): 

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

1660 if not m: 

1661 _item.trivia.indent = indent 

1662 else: 

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

1664 

1665 return self 

1666 

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

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

1669 if not isinstance(_item, Item): 

1670 _item = item(_item) 

1671 

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

1673 

1674 if isinstance(key, Key): 

1675 key = next(iter(key)).key 

1676 _item = self._value[key] 

1677 

1678 if key is not None: 

1679 dict.__setitem__(self, key, _item) 

1680 

1681 return self 

1682 

1683 def is_aot_element(self) -> bool: 

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

1685 return self._is_aot_element 

1686 

1687 def is_super_table(self) -> bool: 

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

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

1690 if self._is_super_table is not None: 

1691 return self._is_super_table 

1692 if not self: 

1693 return False 

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

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

1696 if not isinstance(k, Key): 

1697 k = SingleKey(k) 

1698 index = self.value._map[k] 

1699 if isinstance(index, tuple): 

1700 return False 

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

1702 if ( 

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

1704 or real_key is None 

1705 or real_key.is_dotted() 

1706 ): 

1707 return False 

1708 return True 

1709 

1710 def as_string(self) -> str: 

1711 return self._value.as_string() 

1712 

1713 # Helpers 

1714 

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

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

1717 super().indent(indent) 

1718 

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

1720 if not m: 

1721 indent_str = "" 

1722 else: 

1723 indent_str = m.group(1) 

1724 

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

1726 if not isinstance(item, Whitespace): 

1727 item.trivia.indent = indent_str + item.trivia.indent 

1728 

1729 return self 

1730 

1731 def invalidate_display_name(self): 

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

1733 self.display_name = None 

1734 

1735 for child in self.values(): 

1736 if hasattr(child, "invalidate_display_name"): 

1737 child.invalidate_display_name() 

1738 

1739 def _getstate(self, protocol: int = 3) -> tuple: 

1740 return ( 

1741 self._value, 

1742 self._trivia, 

1743 self._is_aot_element, 

1744 self._is_super_table, 

1745 self.name, 

1746 self.display_name, 

1747 ) 

1748 

1749 

1750class InlineTable(AbstractTable): 

1751 """ 

1752 An inline table literal. 

1753 """ 

1754 

1755 def __init__( 

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

1757 ) -> None: 

1758 super().__init__(value, trivia) 

1759 

1760 self._new = new 

1761 

1762 @property 

1763 def discriminant(self) -> int: 

1764 return 10 

1765 

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

1767 """ 

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

1769 """ 

1770 if not isinstance(_item, Item): 

1771 _item = item(_item, _parent=self) 

1772 

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

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

1775 _item.trivia.indent = " " 

1776 if _item.trivia.comment: 

1777 _item.trivia.comment = "" 

1778 

1779 self._value.append(key, _item) 

1780 

1781 if isinstance(key, Key): 

1782 key = key.key 

1783 

1784 if key is not None: 

1785 dict.__setitem__(self, key, _item) 

1786 

1787 return self 

1788 

1789 def as_string(self) -> str: 

1790 buf = "{" 

1791 last_item_idx = next( 

1792 ( 

1793 i 

1794 for i in range(len(self._value.body) - 1, -1, -1) 

1795 if self._value.body[i][0] is not None 

1796 ), 

1797 None, 

1798 ) 

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

1800 if k is None: 

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

1802 if self._new: 

1803 buf = buf.rstrip(", ") 

1804 else: 

1805 buf = buf.rstrip(",") 

1806 

1807 buf += v.as_string() 

1808 

1809 continue 

1810 

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

1812 buf += ( 

1813 f"{v.trivia.indent}" 

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

1815 f"{k.sep}" 

1816 f"{v.as_string()}" 

1817 f"{v.trivia.comment}" 

1818 f"{v_trivia_trail}" 

1819 ) 

1820 

1821 if last_item_idx is not None and i < last_item_idx: 

1822 buf += "," 

1823 if self._new: 

1824 buf += " " 

1825 

1826 buf += "}" 

1827 

1828 return buf 

1829 

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

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

1832 value.trivia.comment = "" 

1833 super().__setitem__(key, value) 

1834 

1835 def __copy__(self) -> InlineTable: 

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

1837 

1838 def _getstate(self, protocol: int = 3) -> tuple: 

1839 return (self._value, self._trivia) 

1840 

1841 

1842class String(str, Item): 

1843 """ 

1844 A string literal. 

1845 """ 

1846 

1847 def __new__(cls, t, value, original, trivia): 

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

1849 

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

1851 super().__init__(trivia) 

1852 

1853 self._t = t 

1854 self._original = original 

1855 

1856 def unwrap(self) -> str: 

1857 return str(self) 

1858 

1859 @property 

1860 def discriminant(self) -> int: 

1861 return 11 

1862 

1863 @property 

1864 def value(self) -> str: 

1865 return self 

1866 

1867 def as_string(self) -> str: 

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

1869 

1870 def __add__(self: ItemT, other: str) -> ItemT: 

1871 if not isinstance(other, str): 

1872 return NotImplemented 

1873 result = super().__add__(other) 

1874 original = self._original + getattr(other, "_original", other) 

1875 

1876 return self._new(result, original) 

1877 

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

1879 return String(self._t, result, original, self._trivia) 

1880 

1881 def _getstate(self, protocol=3): 

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

1883 

1884 @classmethod 

1885 def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String: 

1886 value = decode(value) 

1887 

1888 invalid = type_.invalid_sequences 

1889 if any(c in value for c in invalid): 

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

1891 

1892 escaped = type_.escaped_sequences 

1893 string_value = escape_string(value, escaped) if escape and escaped else value 

1894 

1895 return cls(type_, decode(value), string_value, Trivia()) 

1896 

1897 

1898class AoT(Item, _CustomList): 

1899 """ 

1900 An array of table literal 

1901 """ 

1902 

1903 def __init__( 

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

1905 ) -> None: 

1906 self.name = name 

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

1908 self._parsed = parsed 

1909 

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

1911 

1912 for table in body: 

1913 self.append(table) 

1914 

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

1916 unwrapped = [] 

1917 for t in self._body: 

1918 if hasattr(t, "unwrap"): 

1919 unwrapped.append(t.unwrap()) 

1920 else: 

1921 unwrapped.append(t) 

1922 return unwrapped 

1923 

1924 @property 

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

1926 return self._body 

1927 

1928 @property 

1929 def discriminant(self) -> int: 

1930 return 12 

1931 

1932 @property 

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

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

1935 

1936 def __len__(self) -> int: 

1937 return len(self._body) 

1938 

1939 @overload 

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

1941 

1942 @overload 

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

1944 

1945 def __getitem__(self, key): 

1946 return self._body[key] 

1947 

1948 def __setitem__(self, key: slice | int, value: Any) -> None: 

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

1950 

1951 def __delitem__(self, key: slice | int) -> None: 

1952 del self._body[key] 

1953 list.__delitem__(self, key) 

1954 

1955 def insert(self, index: int, value: dict) -> None: 

1956 value = item(value, _parent=self) 

1957 if not isinstance(value, Table): 

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

1959 length = len(self) 

1960 if index < 0: 

1961 index += length 

1962 if index < 0: 

1963 index = 0 

1964 elif index >= length: 

1965 index = length 

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

1967 if m: 

1968 indent = m.group(1) 

1969 

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

1971 if not m: 

1972 value.trivia.indent = indent 

1973 else: 

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

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

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

1977 if not self._parsed: 

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

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

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

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

1982 self._body.insert(index, value) 

1983 list.insert(self, index, value) 

1984 

1985 def invalidate_display_name(self): 

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

1987 for child in self: 

1988 if hasattr(child, "invalidate_display_name"): 

1989 child.invalidate_display_name() 

1990 

1991 def as_string(self) -> str: 

1992 b = "" 

1993 for table in self._body: 

1994 b += table.as_string() 

1995 

1996 return b 

1997 

1998 def __repr__(self) -> str: 

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

2000 

2001 def _getstate(self, protocol=3): 

2002 return self._body, self.name, self._parsed 

2003 

2004 

2005class Null(Item): 

2006 """ 

2007 A null item. 

2008 """ 

2009 

2010 def __init__(self) -> None: 

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

2012 

2013 def unwrap(self) -> None: 

2014 return None 

2015 

2016 @property 

2017 def discriminant(self) -> int: 

2018 return -1 

2019 

2020 @property 

2021 def value(self) -> None: 

2022 return None 

2023 

2024 def as_string(self) -> str: 

2025 return "" 

2026 

2027 def _getstate(self, protocol=3) -> tuple: 

2028 return ()