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

1127 statements  

1from __future__ import annotations 

2 

3import abc 

4import copy 

5import dataclasses 

6import math 

7import re 

8import string 

9import sys 

10 

11from datetime import date 

12from datetime import datetime 

13from datetime import time 

14from datetime import tzinfo 

15from enum import Enum 

16from typing import TYPE_CHECKING 

17from typing import Any 

18from typing import Callable 

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 tomlkit import container 

42 

43 

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

45Encoder = Callable[[Any], "Item"] 

46CUSTOM_ENCODERS: list[Encoder] = [] 

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

48 

49 

50@overload 

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

52 

53 

54@overload 

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

56 

57 

58@overload 

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

60 

61 

62@overload 

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

64 

65 

66@overload 

67def item( 

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

69) -> DateTime: ... 

70 

71 

72@overload 

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

74 

75 

76@overload 

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

78 

79 

80@overload 

81def item( 

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

83) -> AoT: ... 

84 

85 

86@overload 

87def item( 

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

89) -> Array: ... 

90 

91 

92@overload 

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

94 

95 

96@overload 

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

98 

99 

100@overload 

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

102 

103 

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

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

106 

107 :Example: 

108 

109 >>> item(42) 

110 42 

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

112 [1, 2, 3] 

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

114 a = 1 

115 b = 2 

116 """ 

117 

118 from tomlkit.container import Container 

119 

120 if isinstance(value, Item): 

121 return value 

122 

123 if isinstance(value, bool): 

124 return Bool(value, Trivia()) 

125 elif isinstance(value, int): 

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

127 elif isinstance(value, float): 

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

129 elif isinstance(value, dict): 

130 table_constructor = ( 

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

132 ) 

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

134 for k, v in sorted( 

135 value.items(), 

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

137 ): 

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

139 

140 return val 

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

142 if ( 

143 value 

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

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

146 ): 

147 a = AoT([]) 

148 table_constructor = Table 

149 else: 

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

151 table_constructor = InlineTable 

152 

153 for v in value: 

154 if isinstance(v, dict): 

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

156 

157 for k, _v in sorted( 

158 v.items(), 

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

160 ): 

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

162 if isinstance(table, InlineTable): 

163 i.trivia.trail = "" 

164 

165 table[k] = i 

166 

167 v = table 

168 

169 a.append(v) 

170 

171 return a 

172 elif isinstance(value, str): 

173 return String.from_raw(value) 

174 elif isinstance(value, datetime): 

175 return DateTime( 

176 value.year, 

177 value.month, 

178 value.day, 

179 value.hour, 

180 value.minute, 

181 value.second, 

182 value.microsecond, 

183 value.tzinfo, 

184 Trivia(), 

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

186 ) 

187 elif isinstance(value, date): 

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

189 elif isinstance(value, time): 

190 return Time( 

191 value.hour, 

192 value.minute, 

193 value.second, 

194 value.microsecond, 

195 value.tzinfo, 

196 Trivia(), 

197 value.isoformat(), 

198 ) 

199 else: 

200 for encoder in CUSTOM_ENCODERS: 

201 try: 

202 rv = encoder(value) 

203 except ConvertError: 

204 pass 

205 else: 

206 if not isinstance(rv, Item): 

207 raise ConvertError( 

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

209 ) 

210 return rv 

211 

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

213 

214 

215class StringType(Enum): 

216 # Single Line Basic 

217 SLB = '"' 

218 # Multi Line Basic 

219 MLB = '"""' 

220 # Single Line Literal 

221 SLL = "'" 

222 # Multi Line Literal 

223 MLL = "'''" 

224 

225 @classmethod 

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

227 return { 

228 (False, False): cls.SLB, 

229 (False, True): cls.MLB, 

230 (True, False): cls.SLL, 

231 (True, True): cls.MLL, 

232 }[(literal, multiline)] 

233 

234 @property 

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

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

237 escaped_in_basic = CONTROL_CHARS | {"\\"} 

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

239 return { 

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

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

242 StringType.SLL: (), 

243 StringType.MLL: (), 

244 }[self] 

245 

246 @property 

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

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

249 forbidden_in_literal = CONTROL_CHARS - {"\t"} 

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

251 return { 

252 StringType.SLB: (), 

253 StringType.MLB: (), 

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

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

256 }[self] 

257 

258 @property 

259 def unit(self) -> str: 

260 return self.value[0] 

261 

262 def is_basic(self) -> bool: 

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

264 

265 def is_literal(self) -> bool: 

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

267 

268 def is_singleline(self) -> bool: 

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

270 

271 def is_multiline(self) -> bool: 

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

273 

274 def toggle(self) -> StringType: 

275 return { 

276 StringType.SLB: StringType.MLB, 

277 StringType.MLB: StringType.SLB, 

278 StringType.SLL: StringType.MLL, 

279 StringType.MLL: StringType.SLL, 

280 }[self] 

281 

282 

283class BoolType(Enum): 

284 TRUE = "true" 

285 FALSE = "false" 

286 

287 def __bool__(self): 

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

289 

290 def __iter__(self): 

291 return iter(self.value) 

292 

293 def __len__(self): 

294 return len(self.value) 

295 

296 

297@dataclasses.dataclass 

298class Trivia: 

299 """ 

300 Trivia information (aka metadata). 

301 """ 

302 

303 # Whitespace before a value. 

304 indent: str = "" 

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

306 comment_ws: str = "" 

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

308 comment: str = "" 

309 # Trailing newline. 

310 trail: str = "\n" 

311 

312 def copy(self) -> Trivia: 

313 return dataclasses.replace(self) 

314 

315 

316class KeyType(Enum): 

317 """ 

318 The type of a Key. 

319 

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

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

322 """ 

323 

324 Bare = "" 

325 Basic = '"' 

326 Literal = "'" 

327 

328 

329class Key(abc.ABC): 

330 """Base class for a key""" 

331 

332 sep: str 

333 _original: str 

334 _keys: list[SingleKey] 

335 _dotted: bool 

336 key: str 

337 

338 @abc.abstractmethod 

339 def __hash__(self) -> int: 

340 pass 

341 

342 @abc.abstractmethod 

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

344 pass 

345 

346 def is_dotted(self) -> bool: 

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

348 return self._dotted 

349 

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

351 return iter(self._keys) 

352 

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

354 """Concatenate keys into a dotted key""" 

355 keys = self._keys + other._keys 

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

357 

358 def is_multi(self) -> bool: 

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

360 return len(self._keys) > 1 

361 

362 def as_string(self) -> str: 

363 """The TOML representation""" 

364 return self._original 

365 

366 def __str__(self) -> str: 

367 return self.as_string() 

368 

369 def __repr__(self) -> str: 

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

371 

372 

373class SingleKey(Key): 

374 """A single key""" 

375 

376 def __init__( 

377 self, 

378 k: str, 

379 t: KeyType | None = None, 

380 sep: str | None = None, 

381 original: str | None = None, 

382 ) -> None: 

383 if not isinstance(k, str): 

384 raise TypeError("Keys must be strings") 

385 

386 if t is None: 

387 if not k or any( 

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

389 ): 

390 t = KeyType.Basic 

391 else: 

392 t = KeyType.Bare 

393 

394 self.t = t 

395 if sep is None: 

396 sep = " = " 

397 

398 self.sep = sep 

399 self.key = k 

400 if original is None: 

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

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

403 

404 self._original = original 

405 self._keys = [self] 

406 self._dotted = False 

407 

408 @property 

409 def delimiter(self) -> str: 

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

411 return self.t.value 

412 

413 def is_bare(self) -> bool: 

414 """Check if the key is bare""" 

415 return self.t == KeyType.Bare 

416 

417 def __hash__(self) -> int: 

418 return hash(self.key) 

419 

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

421 if isinstance(other, Key): 

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

423 

424 return self.key == other 

425 

426 

427class DottedKey(Key): 

428 def __init__( 

429 self, 

430 keys: Iterable[SingleKey], 

431 sep: str | None = None, 

432 original: str | None = None, 

433 ) -> None: 

434 self._keys = list(keys) 

435 if original is None: 

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

437 

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

439 self._original = original 

440 self._dotted = False 

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

442 

443 def __hash__(self) -> int: 

444 return hash(tuple(self._keys)) 

445 

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

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

448 

449 

450class Item: 

451 """ 

452 An item within a TOML document. 

453 """ 

454 

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

456 self._trivia = trivia 

457 

458 @property 

459 def trivia(self) -> Trivia: 

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

461 return self._trivia 

462 

463 @property 

464 def discriminant(self) -> int: 

465 raise NotImplementedError() 

466 

467 def as_string(self) -> str: 

468 """The TOML representation""" 

469 raise NotImplementedError() 

470 

471 @property 

472 def value(self) -> Any: 

473 return self 

474 

475 def unwrap(self) -> Any: 

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

477 raise NotImplementedError() 

478 

479 # Helpers 

480 

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

482 """Attach a comment to this item""" 

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

484 comment = "# " + comment 

485 

486 self._trivia.comment_ws = " " 

487 self._trivia.comment = comment 

488 

489 return self 

490 

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

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

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

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

495 else: 

496 self._trivia.indent = " " * indent 

497 

498 return self 

499 

500 def is_boolean(self) -> bool: 

501 return isinstance(self, Bool) 

502 

503 def is_table(self) -> bool: 

504 return isinstance(self, Table) 

505 

506 def is_inline_table(self) -> bool: 

507 return isinstance(self, InlineTable) 

508 

509 def is_aot(self) -> bool: 

510 return isinstance(self, AoT) 

511 

512 def _getstate(self, protocol=3): 

513 return (self._trivia,) 

514 

515 def __reduce__(self): 

516 return self.__reduce_ex__(2) 

517 

518 def __reduce_ex__(self, protocol): 

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

520 

521 

522class Whitespace(Item): 

523 """ 

524 A whitespace literal. 

525 """ 

526 

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

528 self._s = s 

529 self._fixed = fixed 

530 

531 @property 

532 def s(self) -> str: 

533 return self._s 

534 

535 @property 

536 def value(self) -> str: 

537 """The wrapped string of the whitespace""" 

538 return self._s 

539 

540 @property 

541 def trivia(self) -> Trivia: 

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

543 

544 @property 

545 def discriminant(self) -> int: 

546 return 0 

547 

548 def is_fixed(self) -> bool: 

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

550 return self._fixed 

551 

552 def as_string(self) -> str: 

553 return self._s 

554 

555 def __repr__(self) -> str: 

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

557 

558 def _getstate(self, protocol=3): 

559 return self._s, self._fixed 

560 

561 

562class Comment(Item): 

563 """ 

564 A comment literal. 

565 """ 

566 

567 @property 

568 def discriminant(self) -> int: 

569 return 1 

570 

571 def as_string(self) -> str: 

572 return ( 

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

574 ) 

575 

576 def __str__(self) -> str: 

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

578 

579 

580class Integer(Item, _CustomInt): 

581 """ 

582 An integer literal. 

583 """ 

584 

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

586 return int.__new__(cls, value) 

587 

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

589 super().__init__(trivia) 

590 self._original = value 

591 self._raw = raw 

592 self._sign = False 

593 

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

595 self._sign = True 

596 

597 def unwrap(self) -> int: 

598 return self._original 

599 

600 __int__ = unwrap 

601 

602 def __hash__(self) -> int: 

603 return hash(self.unwrap()) 

604 

605 @property 

606 def discriminant(self) -> int: 

607 return 2 

608 

609 @property 

610 def value(self) -> int: 

611 """The wrapped integer value""" 

612 return self 

613 

614 def as_string(self) -> str: 

615 return self._raw 

616 

617 def _new(self, result): 

618 raw = str(result) 

619 if self._sign and result >= 0: 

620 raw = f"+{raw}" 

621 

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

623 

624 def _getstate(self, protocol=3): 

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

626 

627 # int methods 

628 __abs__ = wrap_method(int.__abs__) 

629 __add__ = wrap_method(int.__add__) 

630 __and__ = wrap_method(int.__and__) 

631 __ceil__ = wrap_method(int.__ceil__) 

632 __eq__ = int.__eq__ 

633 __floor__ = wrap_method(int.__floor__) 

634 __floordiv__ = wrap_method(int.__floordiv__) 

635 __invert__ = wrap_method(int.__invert__) 

636 __le__ = int.__le__ 

637 __lshift__ = wrap_method(int.__lshift__) 

638 __lt__ = int.__lt__ 

639 __mod__ = wrap_method(int.__mod__) 

640 __mul__ = wrap_method(int.__mul__) 

641 __neg__ = wrap_method(int.__neg__) 

642 __or__ = wrap_method(int.__or__) 

643 __pos__ = wrap_method(int.__pos__) 

644 __pow__ = wrap_method(int.__pow__) 

645 __radd__ = wrap_method(int.__radd__) 

646 __rand__ = wrap_method(int.__rand__) 

647 __rfloordiv__ = wrap_method(int.__rfloordiv__) 

648 __rlshift__ = wrap_method(int.__rlshift__) 

649 __rmod__ = wrap_method(int.__rmod__) 

650 __rmul__ = wrap_method(int.__rmul__) 

651 __ror__ = wrap_method(int.__ror__) 

652 __round__ = wrap_method(int.__round__) 

653 __rpow__ = wrap_method(int.__rpow__) 

654 __rrshift__ = wrap_method(int.__rrshift__) 

655 __rshift__ = wrap_method(int.__rshift__) 

656 __rxor__ = wrap_method(int.__rxor__) 

657 __trunc__ = wrap_method(int.__trunc__) 

658 __xor__ = wrap_method(int.__xor__) 

659 

660 def __rtruediv__(self, other): 

661 result = int.__rtruediv__(self, other) 

662 if result is NotImplemented: 

663 return result 

664 return Float._new(self, result) 

665 

666 def __truediv__(self, other): 

667 result = int.__truediv__(self, other) 

668 if result is NotImplemented: 

669 return result 

670 return Float._new(self, result) 

671 

672 

673class Float(Item, _CustomFloat): 

674 """ 

675 A float literal. 

676 """ 

677 

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

679 return float.__new__(cls, value) 

680 

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

682 super().__init__(trivia) 

683 self._original = value 

684 self._raw = raw 

685 self._sign = False 

686 

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

688 self._sign = True 

689 

690 def unwrap(self) -> float: 

691 return self._original 

692 

693 __float__ = unwrap 

694 

695 def __hash__(self) -> int: 

696 return hash(self.unwrap()) 

697 

698 @property 

699 def discriminant(self) -> int: 

700 return 3 

701 

702 @property 

703 def value(self) -> float: 

704 """The wrapped float value""" 

705 return self 

706 

707 def as_string(self) -> str: 

708 return self._raw 

709 

710 def _new(self, result): 

711 raw = str(result) 

712 

713 if self._sign and result >= 0: 

714 raw = f"+{raw}" 

715 

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

717 

718 def _getstate(self, protocol=3): 

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

720 

721 # float methods 

722 __abs__ = wrap_method(float.__abs__) 

723 __add__ = wrap_method(float.__add__) 

724 __eq__ = float.__eq__ 

725 __floordiv__ = wrap_method(float.__floordiv__) 

726 __le__ = float.__le__ 

727 __lt__ = float.__lt__ 

728 __mod__ = wrap_method(float.__mod__) 

729 __mul__ = wrap_method(float.__mul__) 

730 __neg__ = wrap_method(float.__neg__) 

731 __pos__ = wrap_method(float.__pos__) 

732 __pow__ = wrap_method(float.__pow__) 

733 __radd__ = wrap_method(float.__radd__) 

734 __rfloordiv__ = wrap_method(float.__rfloordiv__) 

735 __rmod__ = wrap_method(float.__rmod__) 

736 __rmul__ = wrap_method(float.__rmul__) 

737 __round__ = wrap_method(float.__round__) 

738 __rpow__ = wrap_method(float.__rpow__) 

739 __rtruediv__ = wrap_method(float.__rtruediv__) 

740 __truediv__ = wrap_method(float.__truediv__) 

741 __trunc__ = float.__trunc__ 

742 

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

744 __ceil__ = float.__ceil__ 

745 __floor__ = float.__floor__ 

746 else: 

747 __ceil__ = math.ceil 

748 __floor__ = math.floor 

749 

750 

751class Bool(Item): 

752 """ 

753 A boolean literal. 

754 """ 

755 

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

757 super().__init__(trivia) 

758 

759 self._value = bool(t) 

760 

761 def unwrap(self) -> bool: 

762 return bool(self) 

763 

764 @property 

765 def discriminant(self) -> int: 

766 return 4 

767 

768 @property 

769 def value(self) -> bool: 

770 """The wrapped boolean value""" 

771 return self._value 

772 

773 def as_string(self) -> str: 

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

775 

776 def _getstate(self, protocol=3): 

777 return self._value, self._trivia 

778 

779 def __bool__(self): 

780 return self._value 

781 

782 __nonzero__ = __bool__ 

783 

784 def __eq__(self, other): 

785 if not isinstance(other, bool): 

786 return NotImplemented 

787 

788 return other == self._value 

789 

790 def __hash__(self): 

791 return hash(self._value) 

792 

793 def __repr__(self): 

794 return repr(self._value) 

795 

796 

797class DateTime(Item, datetime): 

798 """ 

799 A datetime literal. 

800 """ 

801 

802 def __new__( 

803 cls, 

804 year: int, 

805 month: int, 

806 day: int, 

807 hour: int, 

808 minute: int, 

809 second: int, 

810 microsecond: int, 

811 tzinfo: tzinfo | None, 

812 *_: Any, 

813 **kwargs: Any, 

814 ) -> datetime: 

815 return datetime.__new__( 

816 cls, 

817 year, 

818 month, 

819 day, 

820 hour, 

821 minute, 

822 second, 

823 microsecond, 

824 tzinfo=tzinfo, 

825 **kwargs, 

826 ) 

827 

828 def __init__( 

829 self, 

830 year: int, 

831 month: int, 

832 day: int, 

833 hour: int, 

834 minute: int, 

835 second: int, 

836 microsecond: int, 

837 tzinfo: tzinfo | None, 

838 trivia: Trivia | None = None, 

839 raw: str | None = None, 

840 **kwargs: Any, 

841 ) -> None: 

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

843 

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

845 

846 def unwrap(self) -> datetime: 

847 ( 

848 year, 

849 month, 

850 day, 

851 hour, 

852 minute, 

853 second, 

854 microsecond, 

855 tzinfo, 

856 _, 

857 _, 

858 ) = self._getstate() 

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

860 

861 @property 

862 def discriminant(self) -> int: 

863 return 5 

864 

865 @property 

866 def value(self) -> datetime: 

867 return self 

868 

869 def as_string(self) -> str: 

870 return self._raw 

871 

872 def __add__(self, other): 

873 if PY38: 

874 result = datetime( 

875 self.year, 

876 self.month, 

877 self.day, 

878 self.hour, 

879 self.minute, 

880 self.second, 

881 self.microsecond, 

882 self.tzinfo, 

883 ).__add__(other) 

884 else: 

885 result = super().__add__(other) 

886 

887 return self._new(result) 

888 

889 def __sub__(self, other): 

890 if PY38: 

891 result = datetime( 

892 self.year, 

893 self.month, 

894 self.day, 

895 self.hour, 

896 self.minute, 

897 self.second, 

898 self.microsecond, 

899 self.tzinfo, 

900 ).__sub__(other) 

901 else: 

902 result = super().__sub__(other) 

903 

904 if isinstance(result, datetime): 

905 result = self._new(result) 

906 

907 return result 

908 

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

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

911 

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

913 result = super().astimezone(tz) 

914 if PY38: 

915 return result 

916 return self._new(result) 

917 

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

919 raw = result.isoformat() 

920 

921 return DateTime( 

922 result.year, 

923 result.month, 

924 result.day, 

925 result.hour, 

926 result.minute, 

927 result.second, 

928 result.microsecond, 

929 result.tzinfo, 

930 self._trivia, 

931 raw, 

932 ) 

933 

934 def _getstate(self, protocol=3): 

935 return ( 

936 self.year, 

937 self.month, 

938 self.day, 

939 self.hour, 

940 self.minute, 

941 self.second, 

942 self.microsecond, 

943 self.tzinfo, 

944 self._trivia, 

945 self._raw, 

946 ) 

947 

948 

949class Date(Item, date): 

950 """ 

951 A date literal. 

952 """ 

953 

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

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

956 

957 def __init__( 

958 self, 

959 year: int, 

960 month: int, 

961 day: int, 

962 trivia: Trivia | None = None, 

963 raw: str = "", 

964 ) -> None: 

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

966 

967 self._raw = raw 

968 

969 def unwrap(self) -> date: 

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

971 return date(year, month, day) 

972 

973 @property 

974 def discriminant(self) -> int: 

975 return 6 

976 

977 @property 

978 def value(self) -> date: 

979 return self 

980 

981 def as_string(self) -> str: 

982 return self._raw 

983 

984 def __add__(self, other): 

985 if PY38: 

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

987 else: 

988 result = super().__add__(other) 

989 

990 return self._new(result) 

991 

992 def __sub__(self, other): 

993 if PY38: 

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

995 else: 

996 result = super().__sub__(other) 

997 

998 if isinstance(result, date): 

999 result = self._new(result) 

1000 

1001 return result 

1002 

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

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

1005 

1006 def _new(self, result): 

1007 raw = result.isoformat() 

1008 

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

1010 

1011 def _getstate(self, protocol=3): 

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

1013 

1014 

1015class Time(Item, time): 

1016 """ 

1017 A time literal. 

1018 """ 

1019 

1020 def __new__( 

1021 cls, 

1022 hour: int, 

1023 minute: int, 

1024 second: int, 

1025 microsecond: int, 

1026 tzinfo: tzinfo | None, 

1027 *_: Any, 

1028 ) -> time: 

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

1030 

1031 def __init__( 

1032 self, 

1033 hour: int, 

1034 minute: int, 

1035 second: int, 

1036 microsecond: int, 

1037 tzinfo: tzinfo | None, 

1038 trivia: Trivia | None = None, 

1039 raw: str = "", 

1040 ) -> None: 

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

1042 

1043 self._raw = raw 

1044 

1045 def unwrap(self) -> time: 

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

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

1048 

1049 @property 

1050 def discriminant(self) -> int: 

1051 return 7 

1052 

1053 @property 

1054 def value(self) -> time: 

1055 return self 

1056 

1057 def as_string(self) -> str: 

1058 return self._raw 

1059 

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

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

1062 

1063 def _new(self, result): 

1064 raw = result.isoformat() 

1065 

1066 return Time( 

1067 result.hour, 

1068 result.minute, 

1069 result.second, 

1070 result.microsecond, 

1071 result.tzinfo, 

1072 self._trivia, 

1073 raw, 

1074 ) 

1075 

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

1077 return ( 

1078 self.hour, 

1079 self.minute, 

1080 self.second, 

1081 self.microsecond, 

1082 self.tzinfo, 

1083 self._trivia, 

1084 self._raw, 

1085 ) 

1086 

1087 

1088class _ArrayItemGroup: 

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

1090 

1091 def __init__( 

1092 self, 

1093 value: Item | None = None, 

1094 indent: Whitespace | None = None, 

1095 comma: Whitespace | None = None, 

1096 comment: Comment | None = None, 

1097 ) -> None: 

1098 self.value = value 

1099 self.indent = indent 

1100 self.comma = comma 

1101 self.comment = comment 

1102 

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

1104 return filter( 

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

1106 ) 

1107 

1108 def __repr__(self) -> str: 

1109 return repr(tuple(self)) 

1110 

1111 def is_whitespace(self) -> bool: 

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

1113 

1114 def __bool__(self) -> bool: 

1115 try: 

1116 next(iter(self)) 

1117 except StopIteration: 

1118 return False 

1119 return True 

1120 

1121 

1122class Array(Item, _CustomList): 

1123 """ 

1124 An array literal 

1125 """ 

1126 

1127 def __init__( 

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

1129 ) -> None: 

1130 super().__init__(trivia) 

1131 list.__init__( 

1132 self, 

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

1134 ) 

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

1136 self._value = self._group_values(value) 

1137 self._multiline = multiline 

1138 self._reindex() 

1139 

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

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

1142 groups = [] 

1143 this_group = _ArrayItemGroup() 

1144 start_new_group = False 

1145 for item in value: 

1146 if isinstance(item, Whitespace): 

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

1148 groups.append(this_group) 

1149 this_group = _ArrayItemGroup(indent=item) 

1150 start_new_group = False 

1151 else: 

1152 if this_group.value is None: 

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

1154 this_group.value = Null() 

1155 this_group.comma = item 

1156 elif isinstance(item, Comment): 

1157 if this_group.value is None: 

1158 this_group.value = Null() 

1159 this_group.comment = item 

1160 # Comments are the last item in a group. 

1161 start_new_group = True 

1162 elif this_group.value is None: 

1163 this_group.value = item 

1164 else: 

1165 groups.append(this_group) 

1166 this_group = _ArrayItemGroup(value=item) 

1167 groups.append(this_group) 

1168 return [group for group in groups if group] 

1169 

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

1171 unwrapped = [] 

1172 for v in self: 

1173 if hasattr(v, "unwrap"): 

1174 unwrapped.append(v.unwrap()) 

1175 else: 

1176 unwrapped.append(v) 

1177 return unwrapped 

1178 

1179 @property 

1180 def discriminant(self) -> int: 

1181 return 8 

1182 

1183 @property 

1184 def value(self) -> list: 

1185 return self 

1186 

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

1188 for v in self._value: 

1189 yield from v 

1190 

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

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

1193 

1194 :Example: 

1195 

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

1197 >>> print(a.as_string()) 

1198 [1, 2, 3] 

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

1200 [ 

1201 1, 

1202 2, 

1203 3, 

1204 ] 

1205 """ 

1206 self._multiline = multiline 

1207 

1208 return self 

1209 

1210 def as_string(self) -> str: 

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

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

1213 

1214 s = "[\n" 

1215 s += "".join( 

1216 self.trivia.indent 

1217 + " " * 4 

1218 + v.value.as_string() 

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

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

1221 + "\n" 

1222 for v in self._value 

1223 if v.value is not None 

1224 ) 

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

1226 

1227 return s 

1228 

1229 def _reindex(self) -> None: 

1230 self._index_map.clear() 

1231 index = 0 

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

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

1234 continue 

1235 self._index_map[index] = i 

1236 index += 1 

1237 

1238 def add_line( 

1239 self, 

1240 *items: Any, 

1241 indent: str = " ", 

1242 comment: str | None = None, 

1243 add_comma: bool = True, 

1244 newline: bool = True, 

1245 ) -> None: 

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

1247 When add_comma is True, only accept actual values and 

1248 ", " will be added between values automatically. 

1249 

1250 :Example: 

1251 

1252 >>> a = array() 

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

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

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

1256 >>> print(a.as_string()) 

1257 [ 

1258 1, 2, 3, 

1259 4, 5, 6, 

1260 ] 

1261 """ 

1262 new_values: list[Item] = [] 

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

1264 if first_indent: 

1265 new_values.append(Whitespace(first_indent)) 

1266 whitespace = "" 

1267 data_values = [] 

1268 for i, el in enumerate(items): 

1269 it = item(el, _parent=self) 

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

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

1272 if not isinstance(it, Whitespace): 

1273 if whitespace: 

1274 new_values.append(Whitespace(whitespace)) 

1275 whitespace = "" 

1276 new_values.append(it) 

1277 data_values.append(it.value) 

1278 if add_comma: 

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

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

1281 new_values.append(Whitespace(" ")) 

1282 elif "," not in it.s: 

1283 whitespace += it.s 

1284 else: 

1285 new_values.append(it) 

1286 if whitespace: 

1287 new_values.append(Whitespace(whitespace)) 

1288 if comment: 

1289 indent = " " if items else "" 

1290 new_values.append( 

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

1292 ) 

1293 list.extend(self, data_values) 

1294 if len(self._value) > 0: 

1295 last_item = self._value[-1] 

1296 last_value_item = next( 

1297 ( 

1298 v 

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

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

1301 ), 

1302 None, 

1303 ) 

1304 if last_value_item is not None: 

1305 last_value_item.comma = Whitespace(",") 

1306 if last_item.is_whitespace(): 

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

1308 else: 

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

1310 else: 

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

1312 self._reindex() 

1313 

1314 def clear(self) -> None: 

1315 """Clear the array.""" 

1316 list.clear(self) 

1317 self._index_map.clear() 

1318 self._value.clear() 

1319 

1320 def __len__(self) -> int: 

1321 return list.__len__(self) 

1322 

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

1324 rv = list.__getitem__(self, index) 

1325 return cast(Item, rv) 

1326 

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

1328 rv = list.__getitem__(self, key) 

1329 if isinstance(rv, Bool): 

1330 return rv.value 

1331 return rv 

1332 

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

1334 it = item(value, _parent=self) 

1335 list.__setitem__(self, key, it) 

1336 if isinstance(key, slice): 

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

1338 if key < 0: 

1339 key += len(self) 

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

1341 

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

1343 it = item(value, _parent=self) 

1344 length = len(self) 

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

1346 list.insert(self, pos, it) 

1347 if pos < 0: 

1348 pos += length 

1349 if pos < 0: 

1350 pos = 0 

1351 

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

1353 default_indent = " " 

1354 if pos < length: 

1355 try: 

1356 idx = self._index_map[pos] 

1357 except KeyError as e: 

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

1359 else: 

1360 idx = len(self._value) 

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

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

1363 idx -= 1 

1364 if ( 

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

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

1367 ): 

1368 default_indent = "\n " 

1369 indent: Item | None = None 

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

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

1372 # Prefer to copy the indentation from the item after 

1373 indent = self._value[idx].indent 

1374 if idx > 0: 

1375 last_item = self._value[idx - 1] 

1376 if indent is None: 

1377 indent = last_item.indent 

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

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

1380 # 2) the array is multiline 

1381 comma = last_item.comma 

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

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

1384 last_item.comma = Whitespace(",") 

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

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

1387 indent = Whitespace(default_indent) 

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

1389 self._value.insert(idx, new_item) 

1390 self._reindex() 

1391 

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

1393 length = len(self) 

1394 list.__delitem__(self, key) 

1395 

1396 if isinstance(key, slice): 

1397 indices_to_remove = list( 

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

1399 ) 

1400 else: 

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

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

1403 try: 

1404 idx = self._index_map[i] 

1405 except KeyError as e: 

1406 if not isinstance(key, slice): 

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

1408 else: 

1409 group_rm = self._value[idx] 

1410 del self._value[idx] 

1411 if ( 

1412 idx == 0 

1413 and len(self._value) > 0 

1414 and self._value[idx].indent 

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

1416 ): 

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

1418 self._value[idx].indent = None 

1419 comma_in_indent = ( 

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

1421 ) 

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

1423 if comma_in_indent and comma_in_comma: 

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

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

1426 if group is not None: 

1427 if group.indent is None: 

1428 group.indent = Whitespace(",") 

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

1430 # Insert the comma after the newline 

1431 try: 

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

1433 group.indent._s = ( 

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

1435 + "," 

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

1437 ) 

1438 except ValueError: 

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

1440 elif not comma_in_indent and not comma_in_comma: 

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

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

1443 group = self._value[j] 

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

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

1446 break 

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

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

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

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

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

1452 if group is not None and ( 

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

1454 ): 

1455 group.indent = group_rm.indent 

1456 

1457 if len(self._value) > 0: 

1458 v = self._value[-1] 

1459 if not v.is_whitespace(): 

1460 # remove the comma of the last item 

1461 v.comma = None 

1462 

1463 self._reindex() 

1464 

1465 def _getstate(self, protocol=3): 

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

1467 

1468 

1469class AbstractTable(Item, _CustomDict): 

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

1471 

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

1473 Item.__init__(self, trivia) 

1474 

1475 self._value = value 

1476 

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

1478 if k is not None: 

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

1480 

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

1482 unwrapped = {} 

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

1484 if isinstance(k, Key): 

1485 k = k.key 

1486 if hasattr(v, "unwrap"): 

1487 v = v.unwrap() 

1488 unwrapped[k] = v 

1489 

1490 return unwrapped 

1491 

1492 @property 

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

1494 return self._value 

1495 

1496 @overload 

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

1498 

1499 @overload 

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

1501 

1502 def append(self, key, value): 

1503 raise NotImplementedError 

1504 

1505 @overload 

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

1507 

1508 @overload 

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

1510 

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

1512 if value is None: 

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

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

1515 raise ValueError(msg) 

1516 

1517 key, value = None, key 

1518 

1519 return self.append(key, value) 

1520 

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

1522 self._value.remove(key) 

1523 

1524 if isinstance(key, Key): 

1525 key = key.key 

1526 

1527 if key is not None: 

1528 dict.__delitem__(self, key) 

1529 

1530 return self 

1531 

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

1533 return self._value.item(key) 

1534 

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

1536 super().setdefault(key, default) 

1537 return self[key] 

1538 

1539 def __str__(self): 

1540 return str(self.value) 

1541 

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

1543 return copy.copy(self) 

1544 

1545 def __repr__(self) -> str: 

1546 return repr(self.value) 

1547 

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

1549 return iter(self._value) 

1550 

1551 def __len__(self) -> int: 

1552 return len(self._value) 

1553 

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

1555 self.remove(key) 

1556 

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

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

1559 

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

1561 if not isinstance(value, Item): 

1562 value = item(value, _parent=self) 

1563 

1564 is_replace = key in self 

1565 self._value[key] = value 

1566 

1567 if key is not None: 

1568 dict.__setitem__(self, key, value) 

1569 

1570 if is_replace: 

1571 return 

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

1573 if not m: 

1574 return 

1575 

1576 indent = m.group(1) 

1577 

1578 if not isinstance(value, Whitespace): 

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

1580 if not m: 

1581 value.trivia.indent = indent 

1582 else: 

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

1584 

1585 

1586class Table(AbstractTable): 

1587 """ 

1588 A table literal. 

1589 """ 

1590 

1591 def __init__( 

1592 self, 

1593 value: container.Container, 

1594 trivia: Trivia, 

1595 is_aot_element: bool, 

1596 is_super_table: bool | None = None, 

1597 name: str | None = None, 

1598 display_name: str | None = None, 

1599 ) -> None: 

1600 super().__init__(value, trivia) 

1601 

1602 self.name = name 

1603 self.display_name = display_name 

1604 self._is_aot_element = is_aot_element 

1605 self._is_super_table = is_super_table 

1606 

1607 @property 

1608 def discriminant(self) -> int: 

1609 return 9 

1610 

1611 def __copy__(self) -> Table: 

1612 return type(self)( 

1613 self._value.copy(), 

1614 self._trivia.copy(), 

1615 self._is_aot_element, 

1616 self._is_super_table, 

1617 self.name, 

1618 self.display_name, 

1619 ) 

1620 

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

1622 """ 

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

1624 """ 

1625 if not isinstance(_item, Item): 

1626 _item = item(_item, _parent=self) 

1627 

1628 self._value.append(key, _item) 

1629 

1630 if isinstance(key, Key): 

1631 key = next(iter(key)).key 

1632 _item = self._value[key] 

1633 

1634 if key is not None: 

1635 dict.__setitem__(self, key, _item) 

1636 

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

1638 if not m: 

1639 return self 

1640 

1641 indent = m.group(1) 

1642 

1643 if not isinstance(_item, Whitespace): 

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

1645 if not m: 

1646 _item.trivia.indent = indent 

1647 else: 

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

1649 

1650 return self 

1651 

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

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

1654 if not isinstance(_item, Item): 

1655 _item = item(_item) 

1656 

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

1658 

1659 if isinstance(key, Key): 

1660 key = next(iter(key)).key 

1661 _item = self._value[key] 

1662 

1663 if key is not None: 

1664 dict.__setitem__(self, key, _item) 

1665 

1666 return self 

1667 

1668 def is_aot_element(self) -> bool: 

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

1670 return self._is_aot_element 

1671 

1672 def is_super_table(self) -> bool: 

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

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

1675 if self._is_super_table is not None: 

1676 return self._is_super_table 

1677 if not self: 

1678 return False 

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

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

1681 if not isinstance(k, Key): 

1682 k = SingleKey(k) 

1683 index = self.value._map[k] 

1684 if isinstance(index, tuple): 

1685 return False 

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

1687 if ( 

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

1689 or real_key is None 

1690 or real_key.is_dotted() 

1691 ): 

1692 return False 

1693 return True 

1694 

1695 def as_string(self) -> str: 

1696 return self._value.as_string() 

1697 

1698 # Helpers 

1699 

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

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

1702 super().indent(indent) 

1703 

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

1705 if not m: 

1706 indent_str = "" 

1707 else: 

1708 indent_str = m.group(1) 

1709 

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

1711 if not isinstance(item, Whitespace): 

1712 item.trivia.indent = indent_str + item.trivia.indent 

1713 

1714 return self 

1715 

1716 def invalidate_display_name(self): 

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

1718 self.display_name = None 

1719 

1720 for child in self.values(): 

1721 if hasattr(child, "invalidate_display_name"): 

1722 child.invalidate_display_name() 

1723 

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

1725 return ( 

1726 self._value, 

1727 self._trivia, 

1728 self._is_aot_element, 

1729 self._is_super_table, 

1730 self.name, 

1731 self.display_name, 

1732 ) 

1733 

1734 

1735class InlineTable(AbstractTable): 

1736 """ 

1737 An inline table literal. 

1738 """ 

1739 

1740 def __init__( 

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

1742 ) -> None: 

1743 super().__init__(value, trivia) 

1744 

1745 self._new = new 

1746 

1747 @property 

1748 def discriminant(self) -> int: 

1749 return 10 

1750 

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

1752 """ 

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

1754 """ 

1755 if not isinstance(_item, Item): 

1756 _item = item(_item, _parent=self) 

1757 

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

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

1760 _item.trivia.indent = " " 

1761 if _item.trivia.comment: 

1762 _item.trivia.comment = "" 

1763 

1764 self._value.append(key, _item) 

1765 

1766 if isinstance(key, Key): 

1767 key = key.key 

1768 

1769 if key is not None: 

1770 dict.__setitem__(self, key, _item) 

1771 

1772 return self 

1773 

1774 def as_string(self) -> str: 

1775 buf = "{" 

1776 last_item_idx = next( 

1777 ( 

1778 i 

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

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

1781 ), 

1782 None, 

1783 ) 

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

1785 if k is None: 

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

1787 if self._new: 

1788 buf = buf.rstrip(", ") 

1789 else: 

1790 buf = buf.rstrip(",") 

1791 

1792 buf += v.as_string() 

1793 

1794 continue 

1795 

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

1797 buf += ( 

1798 f"{v.trivia.indent}" 

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

1800 f"{k.sep}" 

1801 f"{v.as_string()}" 

1802 f"{v.trivia.comment}" 

1803 f"{v_trivia_trail}" 

1804 ) 

1805 

1806 if last_item_idx is not None and i < last_item_idx: 

1807 buf += "," 

1808 if self._new: 

1809 buf += " " 

1810 

1811 buf += "}" 

1812 

1813 return buf 

1814 

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

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

1817 value.trivia.comment = "" 

1818 super().__setitem__(key, value) 

1819 

1820 def __copy__(self) -> InlineTable: 

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

1822 

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

1824 return (self._value, self._trivia) 

1825 

1826 

1827class String(str, Item): 

1828 """ 

1829 A string literal. 

1830 """ 

1831 

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

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

1834 

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

1836 super().__init__(trivia) 

1837 

1838 self._t = t 

1839 self._original = original 

1840 

1841 def unwrap(self) -> str: 

1842 return str(self) 

1843 

1844 @property 

1845 def discriminant(self) -> int: 

1846 return 11 

1847 

1848 @property 

1849 def value(self) -> str: 

1850 return self 

1851 

1852 def as_string(self) -> str: 

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

1854 

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

1856 if not isinstance(other, str): 

1857 return NotImplemented 

1858 result = super().__add__(other) 

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

1860 

1861 return self._new(result, original) 

1862 

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

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

1865 

1866 def _getstate(self, protocol=3): 

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

1868 

1869 @classmethod 

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

1871 value = decode(value) 

1872 

1873 invalid = type_.invalid_sequences 

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

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

1876 

1877 escaped = type_.escaped_sequences 

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

1879 

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

1881 

1882 

1883class AoT(Item, _CustomList): 

1884 """ 

1885 An array of table literal 

1886 """ 

1887 

1888 def __init__( 

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

1890 ) -> None: 

1891 self.name = name 

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

1893 self._parsed = parsed 

1894 

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

1896 

1897 for table in body: 

1898 self.append(table) 

1899 

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

1901 unwrapped = [] 

1902 for t in self._body: 

1903 if hasattr(t, "unwrap"): 

1904 unwrapped.append(t.unwrap()) 

1905 else: 

1906 unwrapped.append(t) 

1907 return unwrapped 

1908 

1909 @property 

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

1911 return self._body 

1912 

1913 @property 

1914 def discriminant(self) -> int: 

1915 return 12 

1916 

1917 @property 

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

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

1920 

1921 def __len__(self) -> int: 

1922 return len(self._body) 

1923 

1924 @overload 

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

1926 

1927 @overload 

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

1929 

1930 def __getitem__(self, key): 

1931 return self._body[key] 

1932 

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

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

1935 

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

1937 del self._body[key] 

1938 list.__delitem__(self, key) 

1939 

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

1941 value = item(value, _parent=self) 

1942 if not isinstance(value, Table): 

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

1944 length = len(self) 

1945 if index < 0: 

1946 index += length 

1947 if index < 0: 

1948 index = 0 

1949 elif index >= length: 

1950 index = length 

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

1952 if m: 

1953 indent = m.group(1) 

1954 

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

1956 if not m: 

1957 value.trivia.indent = indent 

1958 else: 

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

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

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

1962 if not self._parsed: 

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

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

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

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

1967 self._body.insert(index, value) 

1968 list.insert(self, index, value) 

1969 

1970 def invalidate_display_name(self): 

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

1972 for child in self: 

1973 if hasattr(child, "invalidate_display_name"): 

1974 child.invalidate_display_name() 

1975 

1976 def as_string(self) -> str: 

1977 b = "" 

1978 for table in self._body: 

1979 b += table.as_string() 

1980 

1981 return b 

1982 

1983 def __repr__(self) -> str: 

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

1985 

1986 def _getstate(self, protocol=3): 

1987 return self._body, self.name, self._parsed 

1988 

1989 

1990class Null(Item): 

1991 """ 

1992 A null item. 

1993 """ 

1994 

1995 def __init__(self) -> None: 

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

1997 

1998 def unwrap(self) -> None: 

1999 return None 

2000 

2001 @property 

2002 def discriminant(self) -> int: 

2003 return -1 

2004 

2005 @property 

2006 def value(self) -> None: 

2007 return None 

2008 

2009 def as_string(self) -> str: 

2010 return "" 

2011 

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

2013 return ()