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

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

1093 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 t is None: 

384 if not k or any( 

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

386 ): 

387 t = KeyType.Basic 

388 else: 

389 t = KeyType.Bare 

390 

391 self.t = t 

392 if sep is None: 

393 sep = " = " 

394 

395 self.sep = sep 

396 self.key = k 

397 if original is None: 

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

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

400 

401 self._original = original 

402 self._keys = [self] 

403 self._dotted = False 

404 

405 @property 

406 def delimiter(self) -> str: 

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

408 return self.t.value 

409 

410 def is_bare(self) -> bool: 

411 """Check if the key is bare""" 

412 return self.t == KeyType.Bare 

413 

414 def __hash__(self) -> int: 

415 return hash(self.key) 

416 

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

418 if isinstance(other, Key): 

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

420 

421 return self.key == other 

422 

423 

424class DottedKey(Key): 

425 def __init__( 

426 self, 

427 keys: Iterable[SingleKey], 

428 sep: str | None = None, 

429 original: str | None = None, 

430 ) -> None: 

431 self._keys = list(keys) 

432 if original is None: 

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

434 

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

436 self._original = original 

437 self._dotted = False 

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

439 

440 def __hash__(self) -> int: 

441 return hash(tuple(self._keys)) 

442 

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

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

445 

446 

447class Item: 

448 """ 

449 An item within a TOML document. 

450 """ 

451 

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

453 self._trivia = trivia 

454 

455 @property 

456 def trivia(self) -> Trivia: 

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

458 return self._trivia 

459 

460 @property 

461 def discriminant(self) -> int: 

462 raise NotImplementedError() 

463 

464 def as_string(self) -> str: 

465 """The TOML representation""" 

466 raise NotImplementedError() 

467 

468 @property 

469 def value(self) -> Any: 

470 return self 

471 

472 def unwrap(self) -> Any: 

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

474 raise NotImplementedError() 

475 

476 # Helpers 

477 

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

479 """Attach a comment to this item""" 

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

481 comment = "# " + comment 

482 

483 self._trivia.comment_ws = " " 

484 self._trivia.comment = comment 

485 

486 return self 

487 

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

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

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

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

492 else: 

493 self._trivia.indent = " " * indent 

494 

495 return self 

496 

497 def is_boolean(self) -> bool: 

498 return isinstance(self, Bool) 

499 

500 def is_table(self) -> bool: 

501 return isinstance(self, Table) 

502 

503 def is_inline_table(self) -> bool: 

504 return isinstance(self, InlineTable) 

505 

506 def is_aot(self) -> bool: 

507 return isinstance(self, AoT) 

508 

509 def _getstate(self, protocol=3): 

510 return (self._trivia,) 

511 

512 def __reduce__(self): 

513 return self.__reduce_ex__(2) 

514 

515 def __reduce_ex__(self, protocol): 

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

517 

518 

519class Whitespace(Item): 

520 """ 

521 A whitespace literal. 

522 """ 

523 

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

525 self._s = s 

526 self._fixed = fixed 

527 

528 @property 

529 def s(self) -> str: 

530 return self._s 

531 

532 @property 

533 def value(self) -> str: 

534 """The wrapped string of the whitespace""" 

535 return self._s 

536 

537 @property 

538 def trivia(self) -> Trivia: 

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

540 

541 @property 

542 def discriminant(self) -> int: 

543 return 0 

544 

545 def is_fixed(self) -> bool: 

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

547 return self._fixed 

548 

549 def as_string(self) -> str: 

550 return self._s 

551 

552 def __repr__(self) -> str: 

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

554 

555 def _getstate(self, protocol=3): 

556 return self._s, self._fixed 

557 

558 

559class Comment(Item): 

560 """ 

561 A comment literal. 

562 """ 

563 

564 @property 

565 def discriminant(self) -> int: 

566 return 1 

567 

568 def as_string(self) -> str: 

569 return ( 

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

571 ) 

572 

573 def __str__(self) -> str: 

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

575 

576 

577class Integer(Item, _CustomInt): 

578 """ 

579 An integer literal. 

580 """ 

581 

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

583 return int.__new__(cls, value) 

584 

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

586 super().__init__(trivia) 

587 self._original = value 

588 self._raw = raw 

589 self._sign = False 

590 

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

592 self._sign = True 

593 

594 def unwrap(self) -> int: 

595 return self._original 

596 

597 __int__ = unwrap 

598 

599 def __hash__(self) -> int: 

600 return hash(self.unwrap()) 

601 

602 @property 

603 def discriminant(self) -> int: 

604 return 2 

605 

606 @property 

607 def value(self) -> int: 

608 """The wrapped integer value""" 

609 return self 

610 

611 def as_string(self) -> str: 

612 return self._raw 

613 

614 def _new(self, result): 

615 raw = str(result) 

616 if self._sign and result >= 0: 

617 raw = f"+{raw}" 

618 

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

620 

621 def _getstate(self, protocol=3): 

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

623 

624 # int methods 

625 __abs__ = wrap_method(int.__abs__) 

626 __add__ = wrap_method(int.__add__) 

627 __and__ = wrap_method(int.__and__) 

628 __ceil__ = wrap_method(int.__ceil__) 

629 __eq__ = int.__eq__ 

630 __floor__ = wrap_method(int.__floor__) 

631 __floordiv__ = wrap_method(int.__floordiv__) 

632 __invert__ = wrap_method(int.__invert__) 

633 __le__ = int.__le__ 

634 __lshift__ = wrap_method(int.__lshift__) 

635 __lt__ = int.__lt__ 

636 __mod__ = wrap_method(int.__mod__) 

637 __mul__ = wrap_method(int.__mul__) 

638 __neg__ = wrap_method(int.__neg__) 

639 __or__ = wrap_method(int.__or__) 

640 __pos__ = wrap_method(int.__pos__) 

641 __pow__ = wrap_method(int.__pow__) 

642 __radd__ = wrap_method(int.__radd__) 

643 __rand__ = wrap_method(int.__rand__) 

644 __rfloordiv__ = wrap_method(int.__rfloordiv__) 

645 __rlshift__ = wrap_method(int.__rlshift__) 

646 __rmod__ = wrap_method(int.__rmod__) 

647 __rmul__ = wrap_method(int.__rmul__) 

648 __ror__ = wrap_method(int.__ror__) 

649 __round__ = wrap_method(int.__round__) 

650 __rpow__ = wrap_method(int.__rpow__) 

651 __rrshift__ = wrap_method(int.__rrshift__) 

652 __rshift__ = wrap_method(int.__rshift__) 

653 __rxor__ = wrap_method(int.__rxor__) 

654 __trunc__ = wrap_method(int.__trunc__) 

655 __xor__ = wrap_method(int.__xor__) 

656 

657 def __rtruediv__(self, other): 

658 result = int.__rtruediv__(self, other) 

659 if result is NotImplemented: 

660 return result 

661 return Float._new(self, result) 

662 

663 def __truediv__(self, other): 

664 result = int.__truediv__(self, other) 

665 if result is NotImplemented: 

666 return result 

667 return Float._new(self, result) 

668 

669 

670class Float(Item, _CustomFloat): 

671 """ 

672 A float literal. 

673 """ 

674 

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

676 return float.__new__(cls, value) 

677 

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

679 super().__init__(trivia) 

680 self._original = value 

681 self._raw = raw 

682 self._sign = False 

683 

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

685 self._sign = True 

686 

687 def unwrap(self) -> float: 

688 return self._original 

689 

690 __float__ = unwrap 

691 

692 def __hash__(self) -> int: 

693 return hash(self.unwrap()) 

694 

695 @property 

696 def discriminant(self) -> int: 

697 return 3 

698 

699 @property 

700 def value(self) -> float: 

701 """The wrapped float value""" 

702 return self 

703 

704 def as_string(self) -> str: 

705 return self._raw 

706 

707 def _new(self, result): 

708 raw = str(result) 

709 

710 if self._sign and result >= 0: 

711 raw = f"+{raw}" 

712 

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

714 

715 def _getstate(self, protocol=3): 

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

717 

718 # float methods 

719 __abs__ = wrap_method(float.__abs__) 

720 __add__ = wrap_method(float.__add__) 

721 __eq__ = float.__eq__ 

722 __floordiv__ = wrap_method(float.__floordiv__) 

723 __le__ = float.__le__ 

724 __lt__ = float.__lt__ 

725 __mod__ = wrap_method(float.__mod__) 

726 __mul__ = wrap_method(float.__mul__) 

727 __neg__ = wrap_method(float.__neg__) 

728 __pos__ = wrap_method(float.__pos__) 

729 __pow__ = wrap_method(float.__pow__) 

730 __radd__ = wrap_method(float.__radd__) 

731 __rfloordiv__ = wrap_method(float.__rfloordiv__) 

732 __rmod__ = wrap_method(float.__rmod__) 

733 __rmul__ = wrap_method(float.__rmul__) 

734 __round__ = wrap_method(float.__round__) 

735 __rpow__ = wrap_method(float.__rpow__) 

736 __rtruediv__ = wrap_method(float.__rtruediv__) 

737 __truediv__ = wrap_method(float.__truediv__) 

738 __trunc__ = float.__trunc__ 

739 

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

741 __ceil__ = float.__ceil__ 

742 __floor__ = float.__floor__ 

743 else: 

744 __ceil__ = math.ceil 

745 __floor__ = math.floor 

746 

747 

748class Bool(Item): 

749 """ 

750 A boolean literal. 

751 """ 

752 

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

754 super().__init__(trivia) 

755 

756 self._value = bool(t) 

757 

758 def unwrap(self) -> bool: 

759 return bool(self) 

760 

761 @property 

762 def discriminant(self) -> int: 

763 return 4 

764 

765 @property 

766 def value(self) -> bool: 

767 """The wrapped boolean value""" 

768 return self._value 

769 

770 def as_string(self) -> str: 

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

772 

773 def _getstate(self, protocol=3): 

774 return self._value, self._trivia 

775 

776 def __bool__(self): 

777 return self._value 

778 

779 __nonzero__ = __bool__ 

780 

781 def __eq__(self, other): 

782 if not isinstance(other, bool): 

783 return NotImplemented 

784 

785 return other == self._value 

786 

787 def __hash__(self): 

788 return hash(self._value) 

789 

790 def __repr__(self): 

791 return repr(self._value) 

792 

793 

794class DateTime(Item, datetime): 

795 """ 

796 A datetime literal. 

797 """ 

798 

799 def __new__( 

800 cls, 

801 year: int, 

802 month: int, 

803 day: int, 

804 hour: int, 

805 minute: int, 

806 second: int, 

807 microsecond: int, 

808 tzinfo: tzinfo | None, 

809 *_: Any, 

810 **kwargs: Any, 

811 ) -> datetime: 

812 return datetime.__new__( 

813 cls, 

814 year, 

815 month, 

816 day, 

817 hour, 

818 minute, 

819 second, 

820 microsecond, 

821 tzinfo=tzinfo, 

822 **kwargs, 

823 ) 

824 

825 def __init__( 

826 self, 

827 year: int, 

828 month: int, 

829 day: int, 

830 hour: int, 

831 minute: int, 

832 second: int, 

833 microsecond: int, 

834 tzinfo: tzinfo | None, 

835 trivia: Trivia | None = None, 

836 raw: str | None = None, 

837 **kwargs: Any, 

838 ) -> None: 

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

840 

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

842 

843 def unwrap(self) -> datetime: 

844 ( 

845 year, 

846 month, 

847 day, 

848 hour, 

849 minute, 

850 second, 

851 microsecond, 

852 tzinfo, 

853 _, 

854 _, 

855 ) = self._getstate() 

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

857 

858 @property 

859 def discriminant(self) -> int: 

860 return 5 

861 

862 @property 

863 def value(self) -> datetime: 

864 return self 

865 

866 def as_string(self) -> str: 

867 return self._raw 

868 

869 def __add__(self, other): 

870 if PY38: 

871 result = datetime( 

872 self.year, 

873 self.month, 

874 self.day, 

875 self.hour, 

876 self.minute, 

877 self.second, 

878 self.microsecond, 

879 self.tzinfo, 

880 ).__add__(other) 

881 else: 

882 result = super().__add__(other) 

883 

884 return self._new(result) 

885 

886 def __sub__(self, other): 

887 if PY38: 

888 result = datetime( 

889 self.year, 

890 self.month, 

891 self.day, 

892 self.hour, 

893 self.minute, 

894 self.second, 

895 self.microsecond, 

896 self.tzinfo, 

897 ).__sub__(other) 

898 else: 

899 result = super().__sub__(other) 

900 

901 if isinstance(result, datetime): 

902 result = self._new(result) 

903 

904 return result 

905 

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

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

908 

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

910 result = super().astimezone(tz) 

911 if PY38: 

912 return result 

913 return self._new(result) 

914 

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

916 raw = result.isoformat() 

917 

918 return DateTime( 

919 result.year, 

920 result.month, 

921 result.day, 

922 result.hour, 

923 result.minute, 

924 result.second, 

925 result.microsecond, 

926 result.tzinfo, 

927 self._trivia, 

928 raw, 

929 ) 

930 

931 def _getstate(self, protocol=3): 

932 return ( 

933 self.year, 

934 self.month, 

935 self.day, 

936 self.hour, 

937 self.minute, 

938 self.second, 

939 self.microsecond, 

940 self.tzinfo, 

941 self._trivia, 

942 self._raw, 

943 ) 

944 

945 

946class Date(Item, date): 

947 """ 

948 A date literal. 

949 """ 

950 

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

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

953 

954 def __init__( 

955 self, 

956 year: int, 

957 month: int, 

958 day: int, 

959 trivia: Trivia | None = None, 

960 raw: str = "", 

961 ) -> None: 

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

963 

964 self._raw = raw 

965 

966 def unwrap(self) -> date: 

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

968 return date(year, month, day) 

969 

970 @property 

971 def discriminant(self) -> int: 

972 return 6 

973 

974 @property 

975 def value(self) -> date: 

976 return self 

977 

978 def as_string(self) -> str: 

979 return self._raw 

980 

981 def __add__(self, other): 

982 if PY38: 

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

984 else: 

985 result = super().__add__(other) 

986 

987 return self._new(result) 

988 

989 def __sub__(self, other): 

990 if PY38: 

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

992 else: 

993 result = super().__sub__(other) 

994 

995 if isinstance(result, date): 

996 result = self._new(result) 

997 

998 return result 

999 

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

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

1002 

1003 def _new(self, result): 

1004 raw = result.isoformat() 

1005 

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

1007 

1008 def _getstate(self, protocol=3): 

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

1010 

1011 

1012class Time(Item, time): 

1013 """ 

1014 A time literal. 

1015 """ 

1016 

1017 def __new__( 

1018 cls, 

1019 hour: int, 

1020 minute: int, 

1021 second: int, 

1022 microsecond: int, 

1023 tzinfo: tzinfo | None, 

1024 *_: Any, 

1025 ) -> time: 

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

1027 

1028 def __init__( 

1029 self, 

1030 hour: int, 

1031 minute: int, 

1032 second: int, 

1033 microsecond: int, 

1034 tzinfo: tzinfo | None, 

1035 trivia: Trivia | None = None, 

1036 raw: str = "", 

1037 ) -> None: 

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

1039 

1040 self._raw = raw 

1041 

1042 def unwrap(self) -> time: 

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

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

1045 

1046 @property 

1047 def discriminant(self) -> int: 

1048 return 7 

1049 

1050 @property 

1051 def value(self) -> time: 

1052 return self 

1053 

1054 def as_string(self) -> str: 

1055 return self._raw 

1056 

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

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

1059 

1060 def _new(self, result): 

1061 raw = result.isoformat() 

1062 

1063 return Time( 

1064 result.hour, 

1065 result.minute, 

1066 result.second, 

1067 result.microsecond, 

1068 result.tzinfo, 

1069 self._trivia, 

1070 raw, 

1071 ) 

1072 

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

1074 return ( 

1075 self.hour, 

1076 self.minute, 

1077 self.second, 

1078 self.microsecond, 

1079 self.tzinfo, 

1080 self._trivia, 

1081 self._raw, 

1082 ) 

1083 

1084 

1085class _ArrayItemGroup: 

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

1087 

1088 def __init__( 

1089 self, 

1090 value: Item | None = None, 

1091 indent: Whitespace | None = None, 

1092 comma: Whitespace | None = None, 

1093 comment: Comment | None = None, 

1094 ) -> None: 

1095 self.value = value 

1096 self.indent = indent 

1097 self.comma = comma 

1098 self.comment = comment 

1099 

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

1101 return filter( 

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

1103 ) 

1104 

1105 def __repr__(self) -> str: 

1106 return repr(tuple(self)) 

1107 

1108 def is_whitespace(self) -> bool: 

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

1110 

1111 def __bool__(self) -> bool: 

1112 try: 

1113 next(iter(self)) 

1114 except StopIteration: 

1115 return False 

1116 return True 

1117 

1118 

1119class Array(Item, _CustomList): 

1120 """ 

1121 An array literal 

1122 """ 

1123 

1124 def __init__( 

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

1126 ) -> None: 

1127 super().__init__(trivia) 

1128 list.__init__( 

1129 self, 

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

1131 ) 

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

1133 self._value = self._group_values(value) 

1134 self._multiline = multiline 

1135 self._reindex() 

1136 

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

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

1139 groups = [] 

1140 this_group = _ArrayItemGroup() 

1141 for item in value: 

1142 if isinstance(item, Whitespace): 

1143 if "," not in item.s: 

1144 groups.append(this_group) 

1145 this_group = _ArrayItemGroup(indent=item) 

1146 else: 

1147 if this_group.value is None: 

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

1149 this_group.value = Null() 

1150 this_group.comma = item 

1151 elif isinstance(item, Comment): 

1152 if this_group.value is None: 

1153 this_group.value = Null() 

1154 this_group.comment = item 

1155 elif this_group.value is None: 

1156 this_group.value = item 

1157 else: 

1158 groups.append(this_group) 

1159 this_group = _ArrayItemGroup(value=item) 

1160 groups.append(this_group) 

1161 return [group for group in groups if group] 

1162 

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

1164 unwrapped = [] 

1165 for v in self: 

1166 if hasattr(v, "unwrap"): 

1167 unwrapped.append(v.unwrap()) 

1168 else: 

1169 unwrapped.append(v) 

1170 return unwrapped 

1171 

1172 @property 

1173 def discriminant(self) -> int: 

1174 return 8 

1175 

1176 @property 

1177 def value(self) -> list: 

1178 return self 

1179 

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

1181 for v in self._value: 

1182 yield from v 

1183 

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

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

1186 

1187 :Example: 

1188 

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

1190 >>> print(a.as_string()) 

1191 [1, 2, 3] 

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

1193 [ 

1194 1, 

1195 2, 

1196 3, 

1197 ] 

1198 """ 

1199 self._multiline = multiline 

1200 

1201 return self 

1202 

1203 def as_string(self) -> str: 

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

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

1206 

1207 s = "[\n" 

1208 s += "".join( 

1209 self.trivia.indent 

1210 + " " * 4 

1211 + v.value.as_string() 

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

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

1214 + "\n" 

1215 for v in self._value 

1216 if v.value is not None 

1217 ) 

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

1219 

1220 return s 

1221 

1222 def _reindex(self) -> None: 

1223 self._index_map.clear() 

1224 index = 0 

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

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

1227 continue 

1228 self._index_map[index] = i 

1229 index += 1 

1230 

1231 def add_line( 

1232 self, 

1233 *items: Any, 

1234 indent: str = " ", 

1235 comment: str | None = None, 

1236 add_comma: bool = True, 

1237 newline: bool = True, 

1238 ) -> None: 

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

1240 When add_comma is True, only accept actual values and 

1241 ", " will be added between values automatically. 

1242 

1243 :Example: 

1244 

1245 >>> a = array() 

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

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

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

1249 >>> print(a.as_string()) 

1250 [ 

1251 1, 2, 3, 

1252 4, 5, 6, 

1253 ] 

1254 """ 

1255 new_values: list[Item] = [] 

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

1257 if first_indent: 

1258 new_values.append(Whitespace(first_indent)) 

1259 whitespace = "" 

1260 data_values = [] 

1261 for i, el in enumerate(items): 

1262 it = item(el, _parent=self) 

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

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

1265 if not isinstance(it, Whitespace): 

1266 if whitespace: 

1267 new_values.append(Whitespace(whitespace)) 

1268 whitespace = "" 

1269 new_values.append(it) 

1270 data_values.append(it.value) 

1271 if add_comma: 

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

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

1274 new_values.append(Whitespace(" ")) 

1275 elif "," not in it.s: 

1276 whitespace += it.s 

1277 else: 

1278 new_values.append(it) 

1279 if whitespace: 

1280 new_values.append(Whitespace(whitespace)) 

1281 if comment: 

1282 indent = " " if items else "" 

1283 new_values.append( 

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

1285 ) 

1286 list.extend(self, data_values) 

1287 if len(self._value) > 0: 

1288 last_item = self._value[-1] 

1289 last_value_item = next( 

1290 ( 

1291 v 

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

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

1294 ), 

1295 None, 

1296 ) 

1297 if last_value_item is not None: 

1298 last_value_item.comma = Whitespace(",") 

1299 if last_item.is_whitespace(): 

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

1301 else: 

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

1303 else: 

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

1305 self._reindex() 

1306 

1307 def clear(self) -> None: 

1308 """Clear the array.""" 

1309 list.clear(self) 

1310 self._index_map.clear() 

1311 self._value.clear() 

1312 

1313 def __len__(self) -> int: 

1314 return list.__len__(self) 

1315 

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

1317 rv = cast(Item, list.__getitem__(self, key)) 

1318 if rv.is_boolean(): 

1319 return bool(rv) 

1320 return rv 

1321 

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

1323 it = item(value, _parent=self) 

1324 list.__setitem__(self, key, it) 

1325 if isinstance(key, slice): 

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

1327 if key < 0: 

1328 key += len(self) 

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

1330 

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

1332 it = item(value, _parent=self) 

1333 length = len(self) 

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

1335 list.insert(self, pos, it) 

1336 if pos < 0: 

1337 pos += length 

1338 if pos < 0: 

1339 pos = 0 

1340 

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

1342 default_indent = " " 

1343 if pos < length: 

1344 try: 

1345 idx = self._index_map[pos] 

1346 except KeyError as e: 

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

1348 else: 

1349 idx = len(self._value) 

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

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

1352 idx -= 1 

1353 if ( 

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

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

1356 ): 

1357 default_indent = "\n " 

1358 indent: Item | None = None 

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

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

1361 # Prefer to copy the indentation from the item after 

1362 indent = self._value[idx].indent 

1363 if idx > 0: 

1364 last_item = self._value[idx - 1] 

1365 if indent is None: 

1366 indent = last_item.indent 

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

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

1369 # 2) the array is multiline 

1370 comma = last_item.comma 

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

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

1373 last_item.comma = Whitespace(",") 

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

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

1376 indent = Whitespace(default_indent) 

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

1378 self._value.insert(idx, new_item) 

1379 self._reindex() 

1380 

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

1382 length = len(self) 

1383 list.__delitem__(self, key) 

1384 

1385 if isinstance(key, slice): 

1386 indices_to_remove = list( 

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

1388 ) 

1389 else: 

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

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

1392 try: 

1393 idx = self._index_map[i] 

1394 except KeyError as e: 

1395 if not isinstance(key, slice): 

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

1397 else: 

1398 del self._value[idx] 

1399 if ( 

1400 idx == 0 

1401 and len(self._value) > 0 

1402 and self._value[idx].indent 

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

1404 ): 

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

1406 self._value[idx].indent = None 

1407 if len(self._value) > 0: 

1408 v = self._value[-1] 

1409 if not v.is_whitespace(): 

1410 # remove the comma of the last item 

1411 v.comma = None 

1412 

1413 self._reindex() 

1414 

1415 def _getstate(self, protocol=3): 

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

1417 

1418 

1419class AbstractTable(Item, _CustomDict): 

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

1421 

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

1423 Item.__init__(self, trivia) 

1424 

1425 self._value = value 

1426 

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

1428 if k is not None: 

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

1430 

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

1432 unwrapped = {} 

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

1434 if isinstance(k, Key): 

1435 k = k.key 

1436 if hasattr(v, "unwrap"): 

1437 v = v.unwrap() 

1438 unwrapped[k] = v 

1439 

1440 return unwrapped 

1441 

1442 @property 

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

1444 return self._value 

1445 

1446 @overload 

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

1448 

1449 @overload 

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

1451 

1452 def append(self, key, value): 

1453 raise NotImplementedError 

1454 

1455 @overload 

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

1457 

1458 @overload 

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

1460 

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

1462 if value is None: 

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

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

1465 raise ValueError(msg) 

1466 

1467 key, value = None, key 

1468 

1469 return self.append(key, value) 

1470 

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

1472 self._value.remove(key) 

1473 

1474 if isinstance(key, Key): 

1475 key = key.key 

1476 

1477 if key is not None: 

1478 dict.__delitem__(self, key) 

1479 

1480 return self 

1481 

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

1483 super().setdefault(key, default) 

1484 return self[key] 

1485 

1486 def __str__(self): 

1487 return str(self.value) 

1488 

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

1490 return copy.copy(self) 

1491 

1492 def __repr__(self) -> str: 

1493 return repr(self.value) 

1494 

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

1496 return iter(self._value) 

1497 

1498 def __len__(self) -> int: 

1499 return len(self._value) 

1500 

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

1502 self.remove(key) 

1503 

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

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

1506 

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

1508 if not isinstance(value, Item): 

1509 value = item(value, _parent=self) 

1510 

1511 is_replace = key in self 

1512 self._value[key] = value 

1513 

1514 if key is not None: 

1515 dict.__setitem__(self, key, value) 

1516 

1517 if is_replace: 

1518 return 

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

1520 if not m: 

1521 return 

1522 

1523 indent = m.group(1) 

1524 

1525 if not isinstance(value, Whitespace): 

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

1527 if not m: 

1528 value.trivia.indent = indent 

1529 else: 

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

1531 

1532 

1533class Table(AbstractTable): 

1534 """ 

1535 A table literal. 

1536 """ 

1537 

1538 def __init__( 

1539 self, 

1540 value: container.Container, 

1541 trivia: Trivia, 

1542 is_aot_element: bool, 

1543 is_super_table: bool | None = None, 

1544 name: str | None = None, 

1545 display_name: str | None = None, 

1546 ) -> None: 

1547 super().__init__(value, trivia) 

1548 

1549 self.name = name 

1550 self.display_name = display_name 

1551 self._is_aot_element = is_aot_element 

1552 self._is_super_table = is_super_table 

1553 

1554 @property 

1555 def discriminant(self) -> int: 

1556 return 9 

1557 

1558 def __copy__(self) -> Table: 

1559 return type(self)( 

1560 self._value.copy(), 

1561 self._trivia.copy(), 

1562 self._is_aot_element, 

1563 self._is_super_table, 

1564 self.name, 

1565 self.display_name, 

1566 ) 

1567 

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

1569 """ 

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

1571 """ 

1572 if not isinstance(_item, Item): 

1573 _item = item(_item, _parent=self) 

1574 

1575 self._value.append(key, _item) 

1576 

1577 if isinstance(key, Key): 

1578 key = next(iter(key)).key 

1579 _item = self._value[key] 

1580 

1581 if key is not None: 

1582 dict.__setitem__(self, key, _item) 

1583 

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

1585 if not m: 

1586 return self 

1587 

1588 indent = m.group(1) 

1589 

1590 if not isinstance(_item, Whitespace): 

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

1592 if not m: 

1593 _item.trivia.indent = indent 

1594 else: 

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

1596 

1597 return self 

1598 

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

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

1601 if not isinstance(_item, Item): 

1602 _item = item(_item) 

1603 

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

1605 

1606 if isinstance(key, Key): 

1607 key = next(iter(key)).key 

1608 _item = self._value[key] 

1609 

1610 if key is not None: 

1611 dict.__setitem__(self, key, _item) 

1612 

1613 return self 

1614 

1615 def is_aot_element(self) -> bool: 

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

1617 return self._is_aot_element 

1618 

1619 def is_super_table(self) -> bool: 

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

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

1622 if self._is_super_table is not None: 

1623 return self._is_super_table 

1624 if not self: 

1625 return False 

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

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

1628 if not isinstance(k, Key): 

1629 k = SingleKey(k) 

1630 index = self.value._map[k] 

1631 if isinstance(index, tuple): 

1632 return False 

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

1634 if ( 

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

1636 or real_key is None 

1637 or real_key.is_dotted() 

1638 ): 

1639 return False 

1640 return True 

1641 

1642 def as_string(self) -> str: 

1643 return self._value.as_string() 

1644 

1645 # Helpers 

1646 

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

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

1649 super().indent(indent) 

1650 

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

1652 if not m: 

1653 indent_str = "" 

1654 else: 

1655 indent_str = m.group(1) 

1656 

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

1658 if not isinstance(item, Whitespace): 

1659 item.trivia.indent = indent_str + item.trivia.indent 

1660 

1661 return self 

1662 

1663 def invalidate_display_name(self): 

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

1665 self.display_name = None 

1666 

1667 for child in self.values(): 

1668 if hasattr(child, "invalidate_display_name"): 

1669 child.invalidate_display_name() 

1670 

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

1672 return ( 

1673 self._value, 

1674 self._trivia, 

1675 self._is_aot_element, 

1676 self._is_super_table, 

1677 self.name, 

1678 self.display_name, 

1679 ) 

1680 

1681 

1682class InlineTable(AbstractTable): 

1683 """ 

1684 An inline table literal. 

1685 """ 

1686 

1687 def __init__( 

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

1689 ) -> None: 

1690 super().__init__(value, trivia) 

1691 

1692 self._new = new 

1693 

1694 @property 

1695 def discriminant(self) -> int: 

1696 return 10 

1697 

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

1699 """ 

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

1701 """ 

1702 if not isinstance(_item, Item): 

1703 _item = item(_item, _parent=self) 

1704 

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

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

1707 _item.trivia.indent = " " 

1708 if _item.trivia.comment: 

1709 _item.trivia.comment = "" 

1710 

1711 self._value.append(key, _item) 

1712 

1713 if isinstance(key, Key): 

1714 key = key.key 

1715 

1716 if key is not None: 

1717 dict.__setitem__(self, key, _item) 

1718 

1719 return self 

1720 

1721 def as_string(self) -> str: 

1722 buf = "{" 

1723 last_item_idx = next( 

1724 ( 

1725 i 

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

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

1728 ), 

1729 None, 

1730 ) 

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

1732 if k is None: 

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

1734 if self._new: 

1735 buf = buf.rstrip(", ") 

1736 else: 

1737 buf = buf.rstrip(",") 

1738 

1739 buf += v.as_string() 

1740 

1741 continue 

1742 

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

1744 buf += ( 

1745 f"{v.trivia.indent}" 

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

1747 f"{k.sep}" 

1748 f"{v.as_string()}" 

1749 f"{v.trivia.comment}" 

1750 f"{v_trivia_trail}" 

1751 ) 

1752 

1753 if last_item_idx is not None and i < last_item_idx: 

1754 buf += "," 

1755 if self._new: 

1756 buf += " " 

1757 

1758 buf += "}" 

1759 

1760 return buf 

1761 

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

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

1764 value.trivia.comment = "" 

1765 super().__setitem__(key, value) 

1766 

1767 def __copy__(self) -> InlineTable: 

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

1769 

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

1771 return (self._value, self._trivia) 

1772 

1773 

1774class String(str, Item): 

1775 """ 

1776 A string literal. 

1777 """ 

1778 

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

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

1781 

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

1783 super().__init__(trivia) 

1784 

1785 self._t = t 

1786 self._original = original 

1787 

1788 def unwrap(self) -> str: 

1789 return str(self) 

1790 

1791 @property 

1792 def discriminant(self) -> int: 

1793 return 11 

1794 

1795 @property 

1796 def value(self) -> str: 

1797 return self 

1798 

1799 def as_string(self) -> str: 

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

1801 

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

1803 if not isinstance(other, str): 

1804 return NotImplemented 

1805 result = super().__add__(other) 

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

1807 

1808 return self._new(result, original) 

1809 

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

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

1812 

1813 def _getstate(self, protocol=3): 

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

1815 

1816 @classmethod 

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

1818 value = decode(value) 

1819 

1820 invalid = type_.invalid_sequences 

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

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

1823 

1824 escaped = type_.escaped_sequences 

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

1826 

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

1828 

1829 

1830class AoT(Item, _CustomList): 

1831 """ 

1832 An array of table literal 

1833 """ 

1834 

1835 def __init__( 

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

1837 ) -> None: 

1838 self.name = name 

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

1840 self._parsed = parsed 

1841 

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

1843 

1844 for table in body: 

1845 self.append(table) 

1846 

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

1848 unwrapped = [] 

1849 for t in self._body: 

1850 if hasattr(t, "unwrap"): 

1851 unwrapped.append(t.unwrap()) 

1852 else: 

1853 unwrapped.append(t) 

1854 return unwrapped 

1855 

1856 @property 

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

1858 return self._body 

1859 

1860 @property 

1861 def discriminant(self) -> int: 

1862 return 12 

1863 

1864 @property 

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

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

1867 

1868 def __len__(self) -> int: 

1869 return len(self._body) 

1870 

1871 @overload 

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

1873 

1874 @overload 

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

1876 

1877 def __getitem__(self, key): 

1878 return self._body[key] 

1879 

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

1881 raise NotImplementedError 

1882 

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

1884 del self._body[key] 

1885 list.__delitem__(self, key) 

1886 

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

1888 value = item(value, _parent=self) 

1889 if not isinstance(value, Table): 

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

1891 length = len(self) 

1892 if index < 0: 

1893 index += length 

1894 if index < 0: 

1895 index = 0 

1896 elif index >= length: 

1897 index = length 

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

1899 if m: 

1900 indent = m.group(1) 

1901 

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

1903 if not m: 

1904 value.trivia.indent = indent 

1905 else: 

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

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

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

1909 if not self._parsed: 

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

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

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

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

1914 self._body.insert(index, value) 

1915 list.insert(self, index, value) 

1916 

1917 def invalidate_display_name(self): 

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

1919 for child in self: 

1920 if hasattr(child, "invalidate_display_name"): 

1921 child.invalidate_display_name() 

1922 

1923 def as_string(self) -> str: 

1924 b = "" 

1925 for table in self._body: 

1926 b += table.as_string() 

1927 

1928 return b 

1929 

1930 def __repr__(self) -> str: 

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

1932 

1933 def _getstate(self, protocol=3): 

1934 return self._body, self.name, self._parsed 

1935 

1936 

1937class Null(Item): 

1938 """ 

1939 A null item. 

1940 """ 

1941 

1942 def __init__(self) -> None: 

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

1944 

1945 def unwrap(self) -> None: 

1946 return None 

1947 

1948 @property 

1949 def discriminant(self) -> int: 

1950 return -1 

1951 

1952 @property 

1953 def value(self) -> None: 

1954 return None 

1955 

1956 def as_string(self) -> str: 

1957 return "" 

1958 

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

1960 return ()