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

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

1143 statements  

1from __future__ import annotations 

2 

3import abc 

4import copy 

5import dataclasses 

6import inspect 

7import re 

8import string 

9 

10from collections.abc import Collection 

11from collections.abc import Iterable 

12from collections.abc import Iterator 

13from collections.abc import Sequence 

14from datetime import date 

15from datetime import datetime 

16from datetime import time 

17from datetime import tzinfo 

18from enum import Enum 

19from typing import TYPE_CHECKING 

20from typing import Any 

21from typing import TypeVar 

22from typing import cast 

23from typing import overload 

24 

25from tomlkit._compat import PY38 

26from tomlkit._compat import decode 

27from tomlkit._types import _CustomDict 

28from tomlkit._types import _CustomFloat 

29from tomlkit._types import _CustomInt 

30from tomlkit._types import _CustomList 

31from tomlkit._types import wrap_method 

32from tomlkit._utils import CONTROL_CHARS 

33from tomlkit._utils import escape_string 

34from tomlkit.exceptions import ConvertError 

35from tomlkit.exceptions import InvalidStringError 

36 

37 

38if TYPE_CHECKING: 

39 from typing import Protocol 

40 

41 from tomlkit import container 

42 

43 class Encoder(Protocol): 

44 def __call__( 

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

46 ) -> Item: ... 

47 

48 

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

50CUSTOM_ENCODERS: list[Encoder] = [] 

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

52 

53 

54@overload 

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

56 

57 

58@overload 

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

60 

61 

62@overload 

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

64 

65 

66@overload 

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

68 

69 

70@overload 

71def item( 

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

73) -> DateTime: ... 

74 

75 

76@overload 

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

78 

79 

80@overload 

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

82 

83 

84@overload 

85def item( 

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

87) -> AoT: ... 

88 

89 

90@overload 

91def item( 

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

93) -> Array: ... 

94 

95 

96@overload 

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

98 

99 

100@overload 

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

102 

103 

104@overload 

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

106 

107 

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

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

110 

111 :Example: 

112 

113 >>> item(42) 

114 42 

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

116 [1, 2, 3] 

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

118 a = 1 

119 b = 2 

120 """ 

121 

122 from tomlkit.container import Container 

123 

124 if isinstance(value, Item): 

125 return value 

126 

127 if isinstance(value, bool): 

128 return Bool(value, Trivia()) 

129 elif isinstance(value, int): 

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

131 elif isinstance(value, float): 

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

133 elif isinstance(value, dict): 

134 table_constructor = ( 

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

136 ) 

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

138 for k, v in sorted( 

139 value.items(), 

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

141 ): 

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

143 

144 return val 

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

146 if ( 

147 value 

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

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

150 ): 

151 a = AoT([]) 

152 table_constructor = Table 

153 else: 

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

155 table_constructor = InlineTable 

156 

157 for v in value: 

158 if isinstance(v, dict): 

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

160 

161 for k, _v in sorted( 

162 v.items(), 

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

164 ): 

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

166 if isinstance(table, InlineTable): 

167 i.trivia.trail = "" 

168 

169 table[k] = i 

170 

171 v = table 

172 

173 a.append(v) 

174 

175 return a 

176 elif isinstance(value, str): 

177 return String.from_raw(value) 

178 elif isinstance(value, datetime): 

179 return DateTime( 

180 value.year, 

181 value.month, 

182 value.day, 

183 value.hour, 

184 value.minute, 

185 value.second, 

186 value.microsecond, 

187 value.tzinfo, 

188 Trivia(), 

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

190 ) 

191 elif isinstance(value, date): 

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

193 elif isinstance(value, time): 

194 return Time( 

195 value.hour, 

196 value.minute, 

197 value.second, 

198 value.microsecond, 

199 value.tzinfo, 

200 Trivia(), 

201 value.isoformat(), 

202 ) 

203 else: 

204 for encoder in CUSTOM_ENCODERS: 

205 try: 

206 # Check if encoder accepts keyword arguments for backward compatibility 

207 sig = inspect.signature(encoder) 

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

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

210 ): 

211 # New style encoder that can accept additional parameters 

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

213 else: 

214 # Old style encoder that only accepts value 

215 rv = encoder(value) 

216 except ConvertError: 

217 pass 

218 else: 

219 if not isinstance(rv, Item): 

220 raise ConvertError( 

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

222 ) 

223 return rv 

224 

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

226 

227 

228class StringType(Enum): 

229 # Single Line Basic 

230 SLB = '"' 

231 # Multi Line Basic 

232 MLB = '"""' 

233 # Single Line Literal 

234 SLL = "'" 

235 # Multi Line Literal 

236 MLL = "'''" 

237 

238 @classmethod 

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

240 return { 

241 (False, False): cls.SLB, 

242 (False, True): cls.MLB, 

243 (True, False): cls.SLL, 

244 (True, True): cls.MLL, 

245 }[(literal, multiline)] 

246 

247 @property 

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

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

250 escaped_in_basic = CONTROL_CHARS | {"\\"} 

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

252 return { 

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

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

255 StringType.SLL: (), 

256 StringType.MLL: (), 

257 }[self] 

258 

259 @property 

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

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

262 forbidden_in_literal = CONTROL_CHARS - {"\t"} 

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

264 return { 

265 StringType.SLB: (), 

266 StringType.MLB: (), 

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

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

269 }[self] 

270 

271 @property 

272 def unit(self) -> str: 

273 return self.value[0] 

274 

275 def is_basic(self) -> bool: 

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

277 

278 def is_literal(self) -> bool: 

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

280 

281 def is_singleline(self) -> bool: 

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

283 

284 def is_multiline(self) -> bool: 

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

286 

287 def toggle(self) -> StringType: 

288 return { 

289 StringType.SLB: StringType.MLB, 

290 StringType.MLB: StringType.SLB, 

291 StringType.SLL: StringType.MLL, 

292 StringType.MLL: StringType.SLL, 

293 }[self] 

294 

295 

296class BoolType(Enum): 

297 TRUE = "true" 

298 FALSE = "false" 

299 

300 def __bool__(self): 

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

302 

303 def __iter__(self): 

304 return iter(self.value) 

305 

306 def __len__(self): 

307 return len(self.value) 

308 

309 

310@dataclasses.dataclass 

311class Trivia: 

312 """ 

313 Trivia information (aka metadata). 

314 """ 

315 

316 # Whitespace before a value. 

317 indent: str = "" 

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

319 comment_ws: str = "" 

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

321 comment: str = "" 

322 # Trailing newline. 

323 trail: str = "\n" 

324 

325 def copy(self) -> Trivia: 

326 return dataclasses.replace(self) 

327 

328 

329class KeyType(Enum): 

330 """ 

331 The type of a Key. 

332 

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

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

335 """ 

336 

337 Bare = "" 

338 Basic = '"' 

339 Literal = "'" 

340 

341 

342class Key(abc.ABC): 

343 """Base class for a key""" 

344 

345 sep: str 

346 _original: str 

347 _keys: list[SingleKey] 

348 _dotted: bool 

349 key: str 

350 

351 @abc.abstractmethod 

352 def __hash__(self) -> int: 

353 pass 

354 

355 @abc.abstractmethod 

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

357 pass 

358 

359 def is_dotted(self) -> bool: 

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

361 return self._dotted 

362 

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

364 return iter(self._keys) 

365 

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

367 """Concatenate keys into a dotted key""" 

368 keys = self._keys + other._keys 

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

370 

371 def is_multi(self) -> bool: 

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

373 return len(self._keys) > 1 

374 

375 def as_string(self) -> str: 

376 """The TOML representation""" 

377 return self._original 

378 

379 def __str__(self) -> str: 

380 return self.as_string() 

381 

382 def __repr__(self) -> str: 

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

384 

385 

386class SingleKey(Key): 

387 """A single key""" 

388 

389 def __init__( 

390 self, 

391 k: str, 

392 t: KeyType | None = None, 

393 sep: str | None = None, 

394 original: str | None = None, 

395 ) -> None: 

396 if not isinstance(k, str): 

397 raise TypeError("Keys must be strings") 

398 

399 if t is None: 

400 if not k or any( 

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

402 ): 

403 t = KeyType.Basic 

404 else: 

405 t = KeyType.Bare 

406 

407 self.t = t 

408 if sep is None: 

409 sep = " = " 

410 

411 self.sep = sep 

412 self.key = k 

413 if original is None: 

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

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

416 

417 self._original = original 

418 self._keys = [self] 

419 self._dotted = False 

420 

421 @property 

422 def delimiter(self) -> str: 

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

424 return self.t.value 

425 

426 def is_bare(self) -> bool: 

427 """Check if the key is bare""" 

428 return self.t == KeyType.Bare 

429 

430 def __hash__(self) -> int: 

431 return hash(self.key) 

432 

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

434 if isinstance(other, Key): 

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

436 

437 return self.key == other 

438 

439 

440class DottedKey(Key): 

441 def __init__( 

442 self, 

443 keys: Iterable[SingleKey], 

444 sep: str | None = None, 

445 original: str | None = None, 

446 ) -> None: 

447 self._keys = list(keys) 

448 if original is None: 

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

450 

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

452 self._original = original 

453 self._dotted = False 

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

455 

456 def __hash__(self) -> int: 

457 return hash(tuple(self._keys)) 

458 

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

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

461 

462 

463class Item: 

464 """ 

465 An item within a TOML document. 

466 """ 

467 

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

469 self._trivia = trivia 

470 

471 @property 

472 def trivia(self) -> Trivia: 

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

474 return self._trivia 

475 

476 @property 

477 def discriminant(self) -> int: 

478 raise NotImplementedError() 

479 

480 def as_string(self) -> str: 

481 """The TOML representation""" 

482 raise NotImplementedError() 

483 

484 @property 

485 def value(self) -> Any: 

486 return self 

487 

488 def unwrap(self) -> Any: 

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

490 raise NotImplementedError() 

491 

492 # Helpers 

493 

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

495 """Attach a comment to this item""" 

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

497 comment = "# " + comment 

498 

499 self._trivia.comment_ws = " " 

500 self._trivia.comment = comment 

501 

502 return self 

503 

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

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

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

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

508 else: 

509 self._trivia.indent = " " * indent 

510 

511 return self 

512 

513 def is_boolean(self) -> bool: 

514 return isinstance(self, Bool) 

515 

516 def is_table(self) -> bool: 

517 return isinstance(self, Table) 

518 

519 def is_inline_table(self) -> bool: 

520 return isinstance(self, InlineTable) 

521 

522 def is_aot(self) -> bool: 

523 return isinstance(self, AoT) 

524 

525 def _getstate(self, protocol=3): 

526 return (self._trivia,) 

527 

528 def __reduce__(self): 

529 return self.__reduce_ex__(2) 

530 

531 def __reduce_ex__(self, protocol): 

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

533 

534 

535class Whitespace(Item): 

536 """ 

537 A whitespace literal. 

538 """ 

539 

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

541 self._s = s 

542 self._fixed = fixed 

543 

544 @property 

545 def s(self) -> str: 

546 return self._s 

547 

548 @property 

549 def value(self) -> str: 

550 """The wrapped string of the whitespace""" 

551 return self._s 

552 

553 @property 

554 def trivia(self) -> Trivia: 

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

556 

557 @property 

558 def discriminant(self) -> int: 

559 return 0 

560 

561 def is_fixed(self) -> bool: 

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

563 return self._fixed 

564 

565 def as_string(self) -> str: 

566 return self._s 

567 

568 def __repr__(self) -> str: 

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

570 

571 def _getstate(self, protocol=3): 

572 return self._s, self._fixed 

573 

574 

575class Comment(Item): 

576 """ 

577 A comment literal. 

578 """ 

579 

580 @property 

581 def discriminant(self) -> int: 

582 return 1 

583 

584 def as_string(self) -> str: 

585 return ( 

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

587 ) 

588 

589 def __str__(self) -> str: 

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

591 

592 

593class Integer(Item, _CustomInt): 

594 """ 

595 An integer literal. 

596 """ 

597 

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

599 return int.__new__(cls, value) 

600 

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

602 super().__init__(trivia) 

603 self._original = value 

604 self._raw = raw 

605 self._sign = False 

606 

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

608 self._sign = True 

609 

610 def unwrap(self) -> int: 

611 return self._original 

612 

613 __int__ = unwrap 

614 

615 def __hash__(self) -> int: 

616 return hash(self.unwrap()) 

617 

618 @property 

619 def discriminant(self) -> int: 

620 return 2 

621 

622 @property 

623 def value(self) -> int: 

624 """The wrapped integer value""" 

625 return self 

626 

627 def as_string(self) -> str: 

628 return self._raw 

629 

630 def _new(self, result): 

631 raw = str(result) 

632 if self._sign and result >= 0: 

633 raw = f"+{raw}" 

634 

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

636 

637 def _getstate(self, protocol=3): 

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

639 

640 # int methods 

641 __abs__ = wrap_method(int.__abs__) 

642 __add__ = wrap_method(int.__add__) 

643 __and__ = wrap_method(int.__and__) 

644 __ceil__ = wrap_method(int.__ceil__) 

645 __eq__ = int.__eq__ 

646 __floor__ = wrap_method(int.__floor__) 

647 __floordiv__ = wrap_method(int.__floordiv__) 

648 __invert__ = wrap_method(int.__invert__) 

649 __le__ = int.__le__ 

650 __lshift__ = wrap_method(int.__lshift__) 

651 __lt__ = int.__lt__ 

652 __mod__ = wrap_method(int.__mod__) 

653 __mul__ = wrap_method(int.__mul__) 

654 __neg__ = wrap_method(int.__neg__) 

655 __or__ = wrap_method(int.__or__) 

656 __pos__ = wrap_method(int.__pos__) 

657 __pow__ = wrap_method(int.__pow__) 

658 __radd__ = wrap_method(int.__radd__) 

659 __rand__ = wrap_method(int.__rand__) 

660 __rfloordiv__ = wrap_method(int.__rfloordiv__) 

661 __rlshift__ = wrap_method(int.__rlshift__) 

662 __rmod__ = wrap_method(int.__rmod__) 

663 __rmul__ = wrap_method(int.__rmul__) 

664 __ror__ = wrap_method(int.__ror__) 

665 __round__ = wrap_method(int.__round__) 

666 __rpow__ = wrap_method(int.__rpow__) 

667 __rrshift__ = wrap_method(int.__rrshift__) 

668 __rshift__ = wrap_method(int.__rshift__) 

669 __rxor__ = wrap_method(int.__rxor__) 

670 __trunc__ = wrap_method(int.__trunc__) 

671 __xor__ = wrap_method(int.__xor__) 

672 

673 def __rtruediv__(self, other): 

674 result = int.__rtruediv__(self, other) 

675 if result is NotImplemented: 

676 return result 

677 return Float._new(self, result) 

678 

679 def __truediv__(self, other): 

680 result = int.__truediv__(self, other) 

681 if result is NotImplemented: 

682 return result 

683 return Float._new(self, result) 

684 

685 

686class Float(Item, _CustomFloat): 

687 """ 

688 A float literal. 

689 """ 

690 

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

692 return float.__new__(cls, value) 

693 

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

695 super().__init__(trivia) 

696 self._original = value 

697 self._raw = raw 

698 self._sign = False 

699 

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

701 self._sign = True 

702 

703 def unwrap(self) -> float: 

704 return self._original 

705 

706 __float__ = unwrap 

707 

708 def __hash__(self) -> int: 

709 return hash(self.unwrap()) 

710 

711 @property 

712 def discriminant(self) -> int: 

713 return 3 

714 

715 @property 

716 def value(self) -> float: 

717 """The wrapped float value""" 

718 return self 

719 

720 def as_string(self) -> str: 

721 return self._raw 

722 

723 def _new(self, result): 

724 raw = str(result) 

725 

726 if self._sign and result >= 0: 

727 raw = f"+{raw}" 

728 

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

730 

731 def _getstate(self, protocol=3): 

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

733 

734 # float methods 

735 __abs__ = wrap_method(float.__abs__) 

736 __add__ = wrap_method(float.__add__) 

737 __eq__ = float.__eq__ 

738 __floordiv__ = wrap_method(float.__floordiv__) 

739 __le__ = float.__le__ 

740 __lt__ = float.__lt__ 

741 __mod__ = wrap_method(float.__mod__) 

742 __mul__ = wrap_method(float.__mul__) 

743 __neg__ = wrap_method(float.__neg__) 

744 __pos__ = wrap_method(float.__pos__) 

745 __pow__ = wrap_method(float.__pow__) 

746 __radd__ = wrap_method(float.__radd__) 

747 __rfloordiv__ = wrap_method(float.__rfloordiv__) 

748 __rmod__ = wrap_method(float.__rmod__) 

749 __rmul__ = wrap_method(float.__rmul__) 

750 __round__ = wrap_method(float.__round__) 

751 __rpow__ = wrap_method(float.__rpow__) 

752 __rtruediv__ = wrap_method(float.__rtruediv__) 

753 __truediv__ = wrap_method(float.__truediv__) 

754 __trunc__ = float.__trunc__ 

755 

756 __ceil__ = float.__ceil__ 

757 __floor__ = float.__floor__ 

758 

759 

760class Bool(Item): 

761 """ 

762 A boolean literal. 

763 """ 

764 

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

766 super().__init__(trivia) 

767 

768 self._value = bool(t) 

769 

770 def unwrap(self) -> bool: 

771 return bool(self) 

772 

773 @property 

774 def discriminant(self) -> int: 

775 return 4 

776 

777 @property 

778 def value(self) -> bool: 

779 """The wrapped boolean value""" 

780 return self._value 

781 

782 def as_string(self) -> str: 

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

784 

785 def _getstate(self, protocol=3): 

786 return self._value, self._trivia 

787 

788 def __bool__(self): 

789 return self._value 

790 

791 __nonzero__ = __bool__ 

792 

793 def __eq__(self, other): 

794 if not isinstance(other, bool): 

795 return NotImplemented 

796 

797 return other == self._value 

798 

799 def __hash__(self): 

800 return hash(self._value) 

801 

802 def __repr__(self): 

803 return repr(self._value) 

804 

805 

806class DateTime(Item, datetime): 

807 """ 

808 A datetime literal. 

809 """ 

810 

811 def __new__( 

812 cls, 

813 year: int, 

814 month: int, 

815 day: int, 

816 hour: int, 

817 minute: int, 

818 second: int, 

819 microsecond: int, 

820 tzinfo: tzinfo | None, 

821 *_: Any, 

822 **kwargs: Any, 

823 ) -> datetime: 

824 return datetime.__new__( 

825 cls, 

826 year, 

827 month, 

828 day, 

829 hour, 

830 minute, 

831 second, 

832 microsecond, 

833 tzinfo=tzinfo, 

834 **kwargs, 

835 ) 

836 

837 def __init__( 

838 self, 

839 year: int, 

840 month: int, 

841 day: int, 

842 hour: int, 

843 minute: int, 

844 second: int, 

845 microsecond: int, 

846 tzinfo: tzinfo | None, 

847 trivia: Trivia | None = None, 

848 raw: str | None = None, 

849 **kwargs: Any, 

850 ) -> None: 

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

852 

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

854 

855 def unwrap(self) -> datetime: 

856 ( 

857 year, 

858 month, 

859 day, 

860 hour, 

861 minute, 

862 second, 

863 microsecond, 

864 tzinfo, 

865 _, 

866 _, 

867 ) = self._getstate() 

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

869 

870 @property 

871 def discriminant(self) -> int: 

872 return 5 

873 

874 @property 

875 def value(self) -> datetime: 

876 return self 

877 

878 def as_string(self) -> str: 

879 return self._raw 

880 

881 def __add__(self, other): 

882 if PY38: 

883 result = datetime( 

884 self.year, 

885 self.month, 

886 self.day, 

887 self.hour, 

888 self.minute, 

889 self.second, 

890 self.microsecond, 

891 self.tzinfo, 

892 ).__add__(other) 

893 else: 

894 result = super().__add__(other) 

895 

896 return self._new(result) 

897 

898 def __sub__(self, other): 

899 if PY38: 

900 result = datetime( 

901 self.year, 

902 self.month, 

903 self.day, 

904 self.hour, 

905 self.minute, 

906 self.second, 

907 self.microsecond, 

908 self.tzinfo, 

909 ).__sub__(other) 

910 else: 

911 result = super().__sub__(other) 

912 

913 if isinstance(result, datetime): 

914 result = self._new(result) 

915 

916 return result 

917 

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

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

920 

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

922 result = super().astimezone(tz) 

923 if PY38: 

924 return result 

925 return self._new(result) 

926 

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

928 raw = result.isoformat() 

929 

930 return DateTime( 

931 result.year, 

932 result.month, 

933 result.day, 

934 result.hour, 

935 result.minute, 

936 result.second, 

937 result.microsecond, 

938 result.tzinfo, 

939 self._trivia, 

940 raw, 

941 ) 

942 

943 def _getstate(self, protocol=3): 

944 return ( 

945 self.year, 

946 self.month, 

947 self.day, 

948 self.hour, 

949 self.minute, 

950 self.second, 

951 self.microsecond, 

952 self.tzinfo, 

953 self._trivia, 

954 self._raw, 

955 ) 

956 

957 

958class Date(Item, date): 

959 """ 

960 A date literal. 

961 """ 

962 

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

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

965 

966 def __init__( 

967 self, 

968 year: int, 

969 month: int, 

970 day: int, 

971 trivia: Trivia | None = None, 

972 raw: str = "", 

973 ) -> None: 

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

975 

976 self._raw = raw 

977 

978 def unwrap(self) -> date: 

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

980 return date(year, month, day) 

981 

982 @property 

983 def discriminant(self) -> int: 

984 return 6 

985 

986 @property 

987 def value(self) -> date: 

988 return self 

989 

990 def as_string(self) -> str: 

991 return self._raw 

992 

993 def __add__(self, other): 

994 if PY38: 

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

996 else: 

997 result = super().__add__(other) 

998 

999 return self._new(result) 

1000 

1001 def __sub__(self, other): 

1002 if PY38: 

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

1004 else: 

1005 result = super().__sub__(other) 

1006 

1007 if isinstance(result, date): 

1008 result = self._new(result) 

1009 

1010 return result 

1011 

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

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

1014 

1015 def _new(self, result): 

1016 raw = result.isoformat() 

1017 

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

1019 

1020 def _getstate(self, protocol=3): 

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

1022 

1023 

1024class Time(Item, time): 

1025 """ 

1026 A time literal. 

1027 """ 

1028 

1029 def __new__( 

1030 cls, 

1031 hour: int, 

1032 minute: int, 

1033 second: int, 

1034 microsecond: int, 

1035 tzinfo: tzinfo | None, 

1036 *_: Any, 

1037 ) -> time: 

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

1039 

1040 def __init__( 

1041 self, 

1042 hour: int, 

1043 minute: int, 

1044 second: int, 

1045 microsecond: int, 

1046 tzinfo: tzinfo | None, 

1047 trivia: Trivia | None = None, 

1048 raw: str = "", 

1049 ) -> None: 

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

1051 

1052 self._raw = raw 

1053 

1054 def unwrap(self) -> time: 

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

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

1057 

1058 @property 

1059 def discriminant(self) -> int: 

1060 return 7 

1061 

1062 @property 

1063 def value(self) -> time: 

1064 return self 

1065 

1066 def as_string(self) -> str: 

1067 return self._raw 

1068 

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

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

1071 

1072 def _new(self, result): 

1073 raw = result.isoformat() 

1074 

1075 return Time( 

1076 result.hour, 

1077 result.minute, 

1078 result.second, 

1079 result.microsecond, 

1080 result.tzinfo, 

1081 self._trivia, 

1082 raw, 

1083 ) 

1084 

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

1086 return ( 

1087 self.hour, 

1088 self.minute, 

1089 self.second, 

1090 self.microsecond, 

1091 self.tzinfo, 

1092 self._trivia, 

1093 self._raw, 

1094 ) 

1095 

1096 

1097class _ArrayItemGroup: 

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

1099 

1100 def __init__( 

1101 self, 

1102 value: Item | None = None, 

1103 indent: Whitespace | None = None, 

1104 comma: Whitespace | None = None, 

1105 comment: Comment | None = None, 

1106 ) -> None: 

1107 self.value = value 

1108 self.indent = indent 

1109 self.comma = comma 

1110 self.comment = comment 

1111 

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

1113 return filter( 

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

1115 ) 

1116 

1117 def __repr__(self) -> str: 

1118 return repr(tuple(self)) 

1119 

1120 def is_whitespace(self) -> bool: 

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

1122 

1123 def __bool__(self) -> bool: 

1124 try: 

1125 next(iter(self)) 

1126 except StopIteration: 

1127 return False 

1128 return True 

1129 

1130 

1131class Array(Item, _CustomList): 

1132 """ 

1133 An array literal 

1134 """ 

1135 

1136 def __init__( 

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

1138 ) -> None: 

1139 super().__init__(trivia) 

1140 list.__init__( 

1141 self, 

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

1143 ) 

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

1145 self._value = self._group_values(value) 

1146 self._multiline = multiline 

1147 self._reindex() 

1148 

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

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

1151 groups = [] 

1152 this_group = _ArrayItemGroup() 

1153 start_new_group = False 

1154 for item in value: 

1155 if isinstance(item, Whitespace): 

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

1157 groups.append(this_group) 

1158 this_group = _ArrayItemGroup(indent=item) 

1159 start_new_group = False 

1160 else: 

1161 if this_group.value is None: 

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

1163 this_group.value = Null() 

1164 this_group.comma = item 

1165 elif isinstance(item, Comment): 

1166 if this_group.value is None: 

1167 this_group.value = Null() 

1168 this_group.comment = item 

1169 # Comments are the last item in a group. 

1170 start_new_group = True 

1171 elif this_group.value is None: 

1172 this_group.value = item 

1173 else: 

1174 groups.append(this_group) 

1175 this_group = _ArrayItemGroup(value=item) 

1176 groups.append(this_group) 

1177 return [group for group in groups if group] 

1178 

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

1180 unwrapped = [] 

1181 for v in self: 

1182 if hasattr(v, "unwrap"): 

1183 unwrapped.append(v.unwrap()) 

1184 else: 

1185 unwrapped.append(v) 

1186 return unwrapped 

1187 

1188 @property 

1189 def discriminant(self) -> int: 

1190 return 8 

1191 

1192 @property 

1193 def value(self) -> list: 

1194 return self 

1195 

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

1197 for v in self._value: 

1198 yield from v 

1199 

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

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

1202 

1203 :Example: 

1204 

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

1206 >>> print(a.as_string()) 

1207 [1, 2, 3] 

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

1209 [ 

1210 1, 

1211 2, 

1212 3, 

1213 ] 

1214 """ 

1215 self._multiline = multiline 

1216 

1217 return self 

1218 

1219 def as_string(self) -> str: 

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

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

1222 

1223 s = "[\n" 

1224 s += "".join( 

1225 self.trivia.indent 

1226 + " " * 4 

1227 + v.value.as_string() 

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

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

1230 + "\n" 

1231 for v in self._value 

1232 if v.value is not None 

1233 ) 

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

1235 

1236 return s 

1237 

1238 def _reindex(self) -> None: 

1239 self._index_map.clear() 

1240 index = 0 

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

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

1243 continue 

1244 self._index_map[index] = i 

1245 index += 1 

1246 

1247 def add_line( 

1248 self, 

1249 *items: Any, 

1250 indent: str = " ", 

1251 comment: str | None = None, 

1252 add_comma: bool = True, 

1253 newline: bool = True, 

1254 ) -> None: 

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

1256 When add_comma is True, only accept actual values and 

1257 ", " will be added between values automatically. 

1258 

1259 :Example: 

1260 

1261 >>> a = array() 

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

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

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

1265 >>> print(a.as_string()) 

1266 [ 

1267 1, 2, 3, 

1268 4, 5, 6, 

1269 ] 

1270 """ 

1271 new_values: list[Item] = [] 

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

1273 if first_indent: 

1274 new_values.append(Whitespace(first_indent)) 

1275 whitespace = "" 

1276 data_values = [] 

1277 for i, el in enumerate(items): 

1278 it = item(el, _parent=self) 

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

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

1281 if not isinstance(it, Whitespace): 

1282 if whitespace: 

1283 new_values.append(Whitespace(whitespace)) 

1284 whitespace = "" 

1285 new_values.append(it) 

1286 data_values.append(it.value) 

1287 if add_comma: 

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

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

1290 new_values.append(Whitespace(" ")) 

1291 elif "," not in it.s: 

1292 whitespace += it.s 

1293 else: 

1294 new_values.append(it) 

1295 if whitespace: 

1296 new_values.append(Whitespace(whitespace)) 

1297 if comment: 

1298 indent = " " if items else "" 

1299 new_values.append( 

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

1301 ) 

1302 list.extend(self, data_values) 

1303 if len(self._value) > 0: 

1304 last_item = self._value[-1] 

1305 last_value_item = next( 

1306 ( 

1307 v 

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

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

1310 ), 

1311 None, 

1312 ) 

1313 if last_value_item is not None: 

1314 last_value_item.comma = Whitespace(",") 

1315 if last_item.is_whitespace(): 

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

1317 else: 

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

1319 else: 

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

1321 self._reindex() 

1322 

1323 def clear(self) -> None: 

1324 """Clear the array.""" 

1325 list.clear(self) 

1326 self._index_map.clear() 

1327 self._value.clear() 

1328 

1329 def __len__(self) -> int: 

1330 return list.__len__(self) 

1331 

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

1333 rv = list.__getitem__(self, index) 

1334 return cast(Item, rv) 

1335 

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

1337 rv = list.__getitem__(self, key) 

1338 if isinstance(rv, Bool): 

1339 return rv.value 

1340 return rv 

1341 

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

1343 it = item(value, _parent=self) 

1344 list.__setitem__(self, key, it) 

1345 if isinstance(key, slice): 

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

1347 if key < 0: 

1348 key += len(self) 

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

1350 

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

1352 it = item(value, _parent=self) 

1353 length = len(self) 

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

1355 list.insert(self, pos, it) 

1356 if pos < 0: 

1357 pos += length 

1358 if pos < 0: 

1359 pos = 0 

1360 

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

1362 default_indent = " " 

1363 if pos < length: 

1364 try: 

1365 idx = self._index_map[pos] 

1366 except KeyError as e: 

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

1368 else: 

1369 idx = len(self._value) 

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

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

1372 idx -= 1 

1373 if ( 

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

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

1376 ): 

1377 default_indent = "\n " 

1378 indent: Item | None = None 

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

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

1381 # Prefer to copy the indentation from the item after 

1382 indent = self._value[idx].indent 

1383 if idx > 0: 

1384 last_item = self._value[idx - 1] 

1385 if indent is None: 

1386 indent = last_item.indent 

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

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

1389 # 2) the array is multiline 

1390 comma = last_item.comma 

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

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

1393 last_item.comma = Whitespace(",") 

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

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

1396 indent = Whitespace(default_indent) 

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

1398 self._value.insert(idx, new_item) 

1399 self._reindex() 

1400 

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

1402 length = len(self) 

1403 list.__delitem__(self, key) 

1404 

1405 if isinstance(key, slice): 

1406 indices_to_remove = list( 

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

1408 ) 

1409 else: 

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

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

1412 try: 

1413 idx = self._index_map[i] 

1414 except KeyError as e: 

1415 if not isinstance(key, slice): 

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

1417 else: 

1418 group_rm = self._value[idx] 

1419 del self._value[idx] 

1420 if ( 

1421 idx == 0 

1422 and len(self._value) > 0 

1423 and self._value[idx].indent 

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

1425 ): 

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

1427 self._value[idx].indent = None 

1428 comma_in_indent = ( 

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

1430 ) 

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

1432 if comma_in_indent and comma_in_comma: 

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

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

1435 if group is not None: 

1436 if group.indent is None: 

1437 group.indent = Whitespace(",") 

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

1439 # Insert the comma after the newline 

1440 try: 

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

1442 group.indent._s = ( 

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

1444 + "," 

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

1446 ) 

1447 except ValueError: 

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

1449 elif not comma_in_indent and not comma_in_comma: 

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

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

1452 group = self._value[j] 

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

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

1455 break 

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

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

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

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

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

1461 if group is not None and ( 

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

1463 ): 

1464 group.indent = group_rm.indent 

1465 

1466 if len(self._value) > 0: 

1467 v = self._value[-1] 

1468 if not v.is_whitespace(): 

1469 # remove the comma of the last item 

1470 v.comma = None 

1471 

1472 self._reindex() 

1473 

1474 def _getstate(self, protocol=3): 

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

1476 

1477 

1478class AbstractTable(Item, _CustomDict): 

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

1480 

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

1482 Item.__init__(self, trivia) 

1483 

1484 self._value = value 

1485 

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

1487 if k is not None: 

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

1489 

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

1491 unwrapped = {} 

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

1493 if isinstance(k, Key): 

1494 k = k.key 

1495 if hasattr(v, "unwrap"): 

1496 v = v.unwrap() 

1497 unwrapped[k] = v 

1498 

1499 return unwrapped 

1500 

1501 @property 

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

1503 return self._value 

1504 

1505 @overload 

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

1507 

1508 @overload 

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

1510 

1511 def append(self, key, value): 

1512 raise NotImplementedError 

1513 

1514 @overload 

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

1516 

1517 @overload 

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

1519 

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

1521 if value is None: 

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

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

1524 raise ValueError(msg) 

1525 

1526 key, value = None, key 

1527 

1528 return self.append(key, value) 

1529 

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

1531 self._value.remove(key) 

1532 

1533 if isinstance(key, Key): 

1534 key = key.key 

1535 

1536 if key is not None: 

1537 dict.__delitem__(self, key) 

1538 

1539 return self 

1540 

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

1542 return self._value.item(key) 

1543 

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

1545 super().setdefault(key, default) 

1546 return self[key] 

1547 

1548 def __str__(self): 

1549 return str(self.value) 

1550 

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

1552 return copy.copy(self) 

1553 

1554 def __repr__(self) -> str: 

1555 return repr(self.value) 

1556 

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

1558 return iter(self._value) 

1559 

1560 def __len__(self) -> int: 

1561 return len(self._value) 

1562 

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

1564 self.remove(key) 

1565 

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

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

1568 

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

1570 if not isinstance(value, Item): 

1571 value = item(value, _parent=self) 

1572 

1573 is_replace = key in self 

1574 self._value[key] = value 

1575 

1576 if key is not None: 

1577 dict.__setitem__(self, key, value) 

1578 

1579 if is_replace: 

1580 return 

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

1582 if not m: 

1583 return 

1584 

1585 indent = m.group(1) 

1586 

1587 if not isinstance(value, Whitespace): 

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

1589 if not m: 

1590 value.trivia.indent = indent 

1591 else: 

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

1593 

1594 

1595class Table(AbstractTable): 

1596 """ 

1597 A table literal. 

1598 """ 

1599 

1600 def __init__( 

1601 self, 

1602 value: container.Container, 

1603 trivia: Trivia, 

1604 is_aot_element: bool, 

1605 is_super_table: bool | None = None, 

1606 name: str | None = None, 

1607 display_name: str | None = None, 

1608 ) -> None: 

1609 super().__init__(value, trivia) 

1610 

1611 self.name = name 

1612 self.display_name = display_name 

1613 self._is_aot_element = is_aot_element 

1614 self._is_super_table = is_super_table 

1615 

1616 @property 

1617 def discriminant(self) -> int: 

1618 return 9 

1619 

1620 def __copy__(self) -> Table: 

1621 return type(self)( 

1622 self._value.copy(), 

1623 self._trivia.copy(), 

1624 self._is_aot_element, 

1625 self._is_super_table, 

1626 self.name, 

1627 self.display_name, 

1628 ) 

1629 

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

1631 """ 

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

1633 """ 

1634 if not isinstance(_item, Item): 

1635 _item = item(_item, _parent=self) 

1636 

1637 self._value.append(key, _item) 

1638 

1639 if isinstance(key, Key): 

1640 key = next(iter(key)).key 

1641 _item = self._value[key] 

1642 

1643 if key is not None: 

1644 dict.__setitem__(self, key, _item) 

1645 

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

1647 if not m: 

1648 return self 

1649 

1650 indent = m.group(1) 

1651 

1652 if not isinstance(_item, Whitespace): 

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

1654 if not m: 

1655 _item.trivia.indent = indent 

1656 else: 

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

1658 

1659 return self 

1660 

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

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

1663 if not isinstance(_item, Item): 

1664 _item = item(_item) 

1665 

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

1667 

1668 if isinstance(key, Key): 

1669 key = next(iter(key)).key 

1670 _item = self._value[key] 

1671 

1672 if key is not None: 

1673 dict.__setitem__(self, key, _item) 

1674 

1675 return self 

1676 

1677 def is_aot_element(self) -> bool: 

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

1679 return self._is_aot_element 

1680 

1681 def is_super_table(self) -> bool: 

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

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

1684 if self._is_super_table is not None: 

1685 return self._is_super_table 

1686 if not self: 

1687 return False 

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

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

1690 if not isinstance(k, Key): 

1691 k = SingleKey(k) 

1692 index = self.value._map[k] 

1693 if isinstance(index, tuple): 

1694 return False 

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

1696 if ( 

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

1698 or real_key is None 

1699 or real_key.is_dotted() 

1700 ): 

1701 return False 

1702 return True 

1703 

1704 def as_string(self) -> str: 

1705 return self._value.as_string() 

1706 

1707 # Helpers 

1708 

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

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

1711 super().indent(indent) 

1712 

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

1714 if not m: 

1715 indent_str = "" 

1716 else: 

1717 indent_str = m.group(1) 

1718 

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

1720 if not isinstance(item, Whitespace): 

1721 item.trivia.indent = indent_str + item.trivia.indent 

1722 

1723 return self 

1724 

1725 def invalidate_display_name(self): 

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

1727 self.display_name = None 

1728 

1729 for child in self.values(): 

1730 if hasattr(child, "invalidate_display_name"): 

1731 child.invalidate_display_name() 

1732 

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

1734 return ( 

1735 self._value, 

1736 self._trivia, 

1737 self._is_aot_element, 

1738 self._is_super_table, 

1739 self.name, 

1740 self.display_name, 

1741 ) 

1742 

1743 

1744class InlineTable(AbstractTable): 

1745 """ 

1746 An inline table literal. 

1747 """ 

1748 

1749 def __init__( 

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

1751 ) -> None: 

1752 super().__init__(value, trivia) 

1753 

1754 self._new = new 

1755 

1756 @property 

1757 def discriminant(self) -> int: 

1758 return 10 

1759 

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

1761 """ 

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

1763 """ 

1764 if not isinstance(_item, Item): 

1765 _item = item(_item, _parent=self) 

1766 

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

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

1769 _item.trivia.indent = " " 

1770 if _item.trivia.comment: 

1771 _item.trivia.comment = "" 

1772 

1773 self._value.append(key, _item) 

1774 

1775 if isinstance(key, Key): 

1776 key = key.key 

1777 

1778 if key is not None: 

1779 dict.__setitem__(self, key, _item) 

1780 

1781 return self 

1782 

1783 def as_string(self) -> str: 

1784 buf = "{" 

1785 emitted_key = False 

1786 has_explicit_commas = any( 

1787 k is None and isinstance(v, Whitespace) and "," in v.s 

1788 for k, v in self._value.body 

1789 ) 

1790 last_item_idx = next( 

1791 ( 

1792 i 

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

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

1795 ), 

1796 None, 

1797 ) 

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

1799 if k is None: 

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

1801 if not emitted_key: 

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

1803 continue 

1804 

1805 has_following_null = any( 

1806 isinstance(next_v, Null) 

1807 for _, next_v in self._value.body[i + 1 :] 

1808 ) 

1809 has_following_key = any( 

1810 next_k is not None for next_k, _ in self._value.body[i + 1 :] 

1811 ) 

1812 if has_following_null and not has_following_key: 

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

1814 continue 

1815 

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

1817 if self._new: 

1818 buf = buf.rstrip(", ") 

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

1820 buf = buf.rstrip(",") 

1821 

1822 buf += v.as_string() 

1823 

1824 continue 

1825 

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

1827 buf += ( 

1828 f"{v.trivia.indent}" 

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

1830 f"{k.sep}" 

1831 f"{v.as_string()}" 

1832 f"{v.trivia.comment}" 

1833 f"{v_trivia_trail}" 

1834 ) 

1835 emitted_key = True 

1836 

1837 if ( 

1838 not has_explicit_commas 

1839 and last_item_idx is not None 

1840 and i < last_item_idx 

1841 ): 

1842 buf += "," 

1843 if self._new: 

1844 buf += " " 

1845 

1846 buf += "}" 

1847 

1848 return buf 

1849 

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

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

1852 value.trivia.comment = "" 

1853 super().__setitem__(key, value) 

1854 

1855 def __copy__(self) -> InlineTable: 

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

1857 

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

1859 return (self._value, self._trivia) 

1860 

1861 

1862class String(str, Item): 

1863 """ 

1864 A string literal. 

1865 """ 

1866 

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

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

1869 

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

1871 super().__init__(trivia) 

1872 

1873 self._t = t 

1874 self._original = original 

1875 

1876 def unwrap(self) -> str: 

1877 return str(self) 

1878 

1879 @property 

1880 def discriminant(self) -> int: 

1881 return 11 

1882 

1883 @property 

1884 def value(self) -> str: 

1885 return self 

1886 

1887 def as_string(self) -> str: 

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

1889 

1890 @property 

1891 def type(self) -> StringType: 

1892 return self._t 

1893 

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

1895 if not isinstance(other, str): 

1896 return NotImplemented 

1897 result = super().__add__(other) 

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

1899 

1900 return self._new(result, original) 

1901 

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

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

1904 

1905 def _getstate(self, protocol=3): 

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

1907 

1908 @classmethod 

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

1910 value = decode(value) 

1911 

1912 invalid = type_.invalid_sequences 

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

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

1915 

1916 escaped = type_.escaped_sequences 

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

1918 

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

1920 

1921 

1922class AoT(Item, _CustomList): 

1923 """ 

1924 An array of table literal 

1925 """ 

1926 

1927 def __init__( 

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

1929 ) -> None: 

1930 self.name = name 

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

1932 self._parsed = parsed 

1933 

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

1935 

1936 for table in body: 

1937 self.append(table) 

1938 

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

1940 unwrapped = [] 

1941 for t in self._body: 

1942 if hasattr(t, "unwrap"): 

1943 unwrapped.append(t.unwrap()) 

1944 else: 

1945 unwrapped.append(t) 

1946 return unwrapped 

1947 

1948 @property 

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

1950 return self._body 

1951 

1952 @property 

1953 def discriminant(self) -> int: 

1954 return 12 

1955 

1956 @property 

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

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

1959 

1960 def __len__(self) -> int: 

1961 return len(self._body) 

1962 

1963 @overload 

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

1965 

1966 @overload 

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

1968 

1969 def __getitem__(self, key): 

1970 return self._body[key] 

1971 

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

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

1974 

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

1976 del self._body[key] 

1977 list.__delitem__(self, key) 

1978 

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

1980 value = item(value, _parent=self) 

1981 if not isinstance(value, Table): 

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

1983 length = len(self) 

1984 if index < 0: 

1985 index += length 

1986 if index < 0: 

1987 index = 0 

1988 elif index >= length: 

1989 index = length 

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

1991 if m: 

1992 indent = m.group(1) 

1993 

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

1995 if not m: 

1996 value.trivia.indent = indent 

1997 else: 

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

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

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

2001 if not self._parsed: 

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

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

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

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

2006 self._body.insert(index, value) 

2007 list.insert(self, index, value) 

2008 

2009 def invalidate_display_name(self): 

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

2011 for child in self: 

2012 if hasattr(child, "invalidate_display_name"): 

2013 child.invalidate_display_name() 

2014 

2015 def as_string(self) -> str: 

2016 b = "" 

2017 for table in self._body: 

2018 b += table.as_string() 

2019 

2020 return b 

2021 

2022 def __repr__(self) -> str: 

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

2024 

2025 def _getstate(self, protocol=3): 

2026 return self._body, self.name, self._parsed 

2027 

2028 

2029class Null(Item): 

2030 """ 

2031 A null item. 

2032 """ 

2033 

2034 def __init__(self) -> None: 

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

2036 

2037 def unwrap(self) -> None: 

2038 return None 

2039 

2040 @property 

2041 def discriminant(self) -> int: 

2042 return -1 

2043 

2044 @property 

2045 def value(self) -> None: 

2046 return None 

2047 

2048 def as_string(self) -> str: 

2049 return "" 

2050 

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

2052 return ()