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

1051 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 07:01 +0000

1import abc 

2import copy 

3import re 

4import string 

5 

6from datetime import date 

7from datetime import datetime 

8from datetime import time 

9from datetime import tzinfo 

10from enum import Enum 

11from typing import TYPE_CHECKING 

12from typing import Any 

13from typing import Collection 

14from typing import Dict 

15from typing import Iterable 

16from typing import Iterator 

17from typing import List 

18from typing import Optional 

19from typing import Sequence 

20from typing import TypeVar 

21from typing import Union 

22from typing import cast 

23from typing import overload 

24 

25from tomlkit._compat import PY38 

26from tomlkit._compat import decode 

27from tomlkit._utils import CONTROL_CHARS 

28from tomlkit._utils import escape_string 

29from tomlkit.exceptions import InvalidStringError 

30 

31 

32if TYPE_CHECKING: # pragma: no cover 

33 # Define _CustomList and _CustomDict as a workaround for: 

34 # https://github.com/python/mypy/issues/11427 

35 # 

36 # According to this issue, the typeshed contains a "lie" 

37 # (it adds MutableSequence to the ancestry of list and MutableMapping to 

38 # the ancestry of dict) which completely messes with the type inference for 

39 # Table, InlineTable, Array and Container. 

40 # 

41 # Importing from builtins is preferred over simple assignment, see issues: 

42 # https://github.com/python/mypy/issues/8715 

43 # https://github.com/python/mypy/issues/10068 

44 from builtins import dict as _CustomDict # noqa: N812, TC004 

45 from builtins import list as _CustomList # noqa: N812, TC004 

46 

47 # Allow type annotations but break circular imports 

48 from tomlkit import container 

49else: 

50 from collections.abc import MutableMapping 

51 from collections.abc import MutableSequence 

52 

53 class _CustomList(MutableSequence, list): 

54 """Adds MutableSequence mixin while pretending to be a builtin list""" 

55 

56 class _CustomDict(MutableMapping, dict): 

57 """Adds MutableMapping mixin while pretending to be a builtin dict""" 

58 

59 

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

61 

62 

63@overload 

64def item( 

65 value: bool, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

66) -> "Bool": 

67 ... 

68 

69 

70@overload 

71def item( 

72 value: int, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

73) -> "Integer": 

74 ... 

75 

76 

77@overload 

78def item( 

79 value: float, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

80) -> "Float": 

81 ... 

82 

83 

84@overload 

85def item( 

86 value: str, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

87) -> "String": 

88 ... 

89 

90 

91@overload 

92def item( 

93 value: datetime, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

94) -> "DateTime": 

95 ... 

96 

97 

98@overload 

99def item( 

100 value: date, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

101) -> "Date": 

102 ... 

103 

104 

105@overload 

106def item( 

107 value: time, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

108) -> "Time": 

109 ... 

110 

111 

112@overload 

113def item( 

114 value: Sequence[dict], _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

115) -> "AoT": 

116 ... 

117 

118 

119@overload 

120def item( 

121 value: Sequence, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

122) -> "Array": 

123 ... 

124 

125 

126@overload 

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

128 ... 

129 

130 

131@overload 

132def item( 

133 value: dict, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

134) -> "Table": 

135 ... 

136 

137 

138@overload 

139def item( 

140 value: ItemT, _parent: Optional["Item"] = ..., _sort_keys: bool = ... 

141) -> ItemT: 

142 ... 

143 

144 

145def item( 

146 value: Any, _parent: Optional["Item"] = None, _sort_keys: bool = False 

147) -> "Item": 

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

149 

150 :Example: 

151 

152 >>> item(42) 

153 42 

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

155 [1, 2, 3] 

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

157 a = 1 

158 b = 2 

159 """ 

160 

161 from tomlkit.container import Container 

162 

163 if isinstance(value, Item): 

164 return value 

165 

166 if isinstance(value, bool): 

167 return Bool(value, Trivia()) 

168 elif isinstance(value, int): 

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

170 elif isinstance(value, float): 

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

172 elif isinstance(value, dict): 

173 table_constructor = ( 

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

175 ) 

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

177 for k, v in sorted( 

178 value.items(), 

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

180 ): 

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

182 

183 return val 

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

185 if ( 

186 value 

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

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

189 ): 

190 a = AoT([]) 

191 table_constructor = Table 

192 else: 

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

194 table_constructor = InlineTable 

195 

196 for v in value: 

197 if isinstance(v, dict): 

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

199 

200 for k, _v in sorted( 

201 v.items(), 

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

203 ): 

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

205 if isinstance(table, InlineTable): 

206 i.trivia.trail = "" 

207 

208 table[k] = i 

209 

210 v = table 

211 

212 a.append(v) 

213 

214 return a 

215 elif isinstance(value, str): 

216 return String.from_raw(value) 

217 elif isinstance(value, datetime): 

218 return DateTime( 

219 value.year, 

220 value.month, 

221 value.day, 

222 value.hour, 

223 value.minute, 

224 value.second, 

225 value.microsecond, 

226 value.tzinfo, 

227 Trivia(), 

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

229 ) 

230 elif isinstance(value, date): 

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

232 elif isinstance(value, time): 

233 return Time( 

234 value.hour, 

235 value.minute, 

236 value.second, 

237 value.microsecond, 

238 value.tzinfo, 

239 Trivia(), 

240 value.isoformat(), 

241 ) 

242 

243 raise ValueError(f"Invalid type {type(value)}") 

244 

245 

246class StringType(Enum): 

247 # Single Line Basic 

248 SLB = '"' 

249 # Multi Line Basic 

250 MLB = '"""' 

251 # Single Line Literal 

252 SLL = "'" 

253 # Multi Line Literal 

254 MLL = "'''" 

255 

256 @classmethod 

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

258 return { 

259 (False, False): cls.SLB, 

260 (False, True): cls.MLB, 

261 (True, False): cls.SLL, 

262 (True, True): cls.MLL, 

263 }[(literal, multiline)] 

264 

265 @property 

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

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

268 escaped_in_basic = CONTROL_CHARS | {"\\"} 

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

270 return { 

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

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

273 StringType.SLL: (), 

274 StringType.MLL: (), 

275 }[self] 

276 

277 @property 

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

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

280 forbidden_in_literal = CONTROL_CHARS - {"\t"} 

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

282 return { 

283 StringType.SLB: (), 

284 StringType.MLB: (), 

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

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

287 }[self] 

288 

289 @property 

290 def unit(self) -> str: 

291 return self.value[0] 

292 

293 def is_basic(self) -> bool: 

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

295 

296 def is_literal(self) -> bool: 

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

298 

299 def is_singleline(self) -> bool: 

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

301 

302 def is_multiline(self) -> bool: 

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

304 

305 def toggle(self) -> "StringType": 

306 return { 

307 StringType.SLB: StringType.MLB, 

308 StringType.MLB: StringType.SLB, 

309 StringType.SLL: StringType.MLL, 

310 StringType.MLL: StringType.SLL, 

311 }[self] 

312 

313 

314class BoolType(Enum): 

315 TRUE = "true" 

316 FALSE = "false" 

317 

318 def __bool__(self): 

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

320 

321 def __iter__(self): 

322 return iter(self.value) 

323 

324 def __len__(self): 

325 return len(self.value) 

326 

327 

328class Trivia: 

329 """ 

330 Trivia information (aka metadata). 

331 """ 

332 

333 def __init__( 

334 self, 

335 indent: str = None, 

336 comment_ws: str = None, 

337 comment: str = None, 

338 trail: str = None, 

339 ) -> None: 

340 # Whitespace before a value. 

341 self.indent = indent or "" 

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

343 self.comment_ws = comment_ws or "" 

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

345 self.comment = comment or "" 

346 # Trailing newline. 

347 if trail is None: 

348 trail = "\n" 

349 

350 self.trail = trail 

351 

352 def copy(self) -> "Trivia": 

353 return type(self)(self.indent, self.comment_ws, self.comment, self.trail) 

354 

355 

356class KeyType(Enum): 

357 """ 

358 The type of a Key. 

359 

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

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

362 """ 

363 

364 Bare = "" 

365 Basic = '"' 

366 Literal = "'" 

367 

368 

369class Key(abc.ABC): 

370 """Base class for a key""" 

371 

372 sep: str 

373 _original: str 

374 _keys: List["SingleKey"] 

375 _dotted: bool 

376 key: str 

377 

378 @abc.abstractmethod 

379 def __hash__(self) -> int: 

380 pass 

381 

382 @abc.abstractmethod 

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

384 pass 

385 

386 def is_dotted(self) -> bool: 

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

388 return self._dotted 

389 

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

391 return iter(self._keys) 

392 

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

394 """Concatenate keys into a dotted key""" 

395 keys = self._keys + other._keys 

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

397 

398 def is_multi(self) -> bool: 

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

400 return len(self._keys) > 1 

401 

402 def as_string(self) -> str: 

403 """The TOML representation""" 

404 return self._original 

405 

406 def __str__(self) -> str: 

407 return self.as_string() 

408 

409 def __repr__(self) -> str: 

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

411 

412 

413class SingleKey(Key): 

414 """A single key""" 

415 

416 def __init__( 

417 self, 

418 k: str, 

419 t: Optional[KeyType] = None, 

420 sep: Optional[str] = None, 

421 original: Optional[str] = None, 

422 ) -> None: 

423 if t is None: 

424 if not k or any( 

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

426 ): 

427 t = KeyType.Basic 

428 else: 

429 t = KeyType.Bare 

430 

431 self.t = t 

432 if sep is None: 

433 sep = " = " 

434 

435 self.sep = sep 

436 self.key = k 

437 if original is None: 

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

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

440 

441 self._original = original 

442 self._keys = [self] 

443 self._dotted = False 

444 

445 @property 

446 def delimiter(self) -> str: 

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

448 return self.t.value 

449 

450 def is_bare(self) -> bool: 

451 """Check if the key is bare""" 

452 return self.t == KeyType.Bare 

453 

454 def __hash__(self) -> int: 

455 return hash(self.key) 

456 

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

458 if isinstance(other, Key): 

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

460 

461 return self.key == other 

462 

463 

464class DottedKey(Key): 

465 def __init__( 

466 self, 

467 keys: Iterable[Key], 

468 sep: Optional[str] = None, 

469 original: Optional[str] = None, 

470 ) -> None: 

471 self._keys = list(keys) 

472 if original is None: 

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

474 

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

476 self._original = original 

477 self._dotted = False 

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

479 

480 def __hash__(self) -> int: 

481 return hash(tuple(self._keys)) 

482 

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

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

485 

486 

487class Item: 

488 """ 

489 An item within a TOML document. 

490 """ 

491 

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

493 self._trivia = trivia 

494 

495 @property 

496 def trivia(self) -> Trivia: 

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

498 return self._trivia 

499 

500 @property 

501 def discriminant(self) -> int: 

502 raise NotImplementedError() 

503 

504 def as_string(self) -> str: 

505 """The TOML representation""" 

506 raise NotImplementedError() 

507 

508 @property 

509 def value(self) -> Any: 

510 return self 

511 

512 def unwrap(self) -> Any: 

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

514 raise NotImplementedError() 

515 

516 # Helpers 

517 

518 def comment(self, comment: str) -> "Item": 

519 """Attach a comment to this item""" 

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

521 comment = "# " + comment 

522 

523 self._trivia.comment_ws = " " 

524 self._trivia.comment = comment 

525 

526 return self 

527 

528 def indent(self, indent: int) -> "Item": 

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

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

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

532 else: 

533 self._trivia.indent = " " * indent 

534 

535 return self 

536 

537 def is_boolean(self) -> bool: 

538 return isinstance(self, Bool) 

539 

540 def is_table(self) -> bool: 

541 return isinstance(self, Table) 

542 

543 def is_inline_table(self) -> bool: 

544 return isinstance(self, InlineTable) 

545 

546 def is_aot(self) -> bool: 

547 return isinstance(self, AoT) 

548 

549 def _getstate(self, protocol=3): 

550 return (self._trivia,) 

551 

552 def __reduce__(self): 

553 return self.__reduce_ex__(2) 

554 

555 def __reduce_ex__(self, protocol): 

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

557 

558 

559class Whitespace(Item): 

560 """ 

561 A whitespace literal. 

562 """ 

563 

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

565 self._s = s 

566 self._fixed = fixed 

567 

568 @property 

569 def s(self) -> str: 

570 return self._s 

571 

572 @property 

573 def value(self) -> str: 

574 """The wrapped string of the whitespace""" 

575 return self._s 

576 

577 @property 

578 def trivia(self) -> Trivia: 

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

580 

581 @property 

582 def discriminant(self) -> int: 

583 return 0 

584 

585 def is_fixed(self) -> bool: 

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

587 return self._fixed 

588 

589 def as_string(self) -> str: 

590 return self._s 

591 

592 def __repr__(self) -> str: 

593 return f"<{self.__class__.__name__} {repr(self._s)}>" 

594 

595 def _getstate(self, protocol=3): 

596 return self._s, self._fixed 

597 

598 

599class Comment(Item): 

600 """ 

601 A comment literal. 

602 """ 

603 

604 @property 

605 def discriminant(self) -> int: 

606 return 1 

607 

608 def as_string(self) -> str: 

609 return ( 

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

611 ) 

612 

613 def __str__(self) -> str: 

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

615 

616 

617class Integer(int, Item): 

618 """ 

619 An integer literal. 

620 """ 

621 

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

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

624 

625 def __init__(self, _: int, trivia: Trivia, raw: str) -> None: 

626 super().__init__(trivia) 

627 

628 self._raw = raw 

629 self._sign = False 

630 

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

632 self._sign = True 

633 

634 def unwrap(self) -> int: 

635 return int(self) 

636 

637 @property 

638 def discriminant(self) -> int: 

639 return 2 

640 

641 @property 

642 def value(self) -> int: 

643 """The wrapped integer value""" 

644 return self 

645 

646 def as_string(self) -> str: 

647 return self._raw 

648 

649 def __add__(self, other): 

650 return self._new(int(self._raw) + other) 

651 

652 def __radd__(self, other): 

653 result = super().__radd__(other) 

654 

655 if isinstance(other, Integer): 

656 return self._new(result) 

657 

658 return result 

659 

660 def __sub__(self, other): 

661 result = super().__sub__(other) 

662 

663 return self._new(result) 

664 

665 def __rsub__(self, other): 

666 result = super().__rsub__(other) 

667 

668 if isinstance(other, Integer): 

669 return self._new(result) 

670 

671 return result 

672 

673 def _new(self, result): 

674 raw = str(result) 

675 if self._sign: 

676 sign = "+" if result >= 0 else "-" 

677 raw = sign + raw 

678 

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

680 

681 def _getstate(self, protocol=3): 

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

683 

684 

685class Float(float, Item): 

686 """ 

687 A float literal. 

688 """ 

689 

690 def __new__(cls, value: float, trivia: Trivia, raw: str) -> Integer: 

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

692 

693 def __init__(self, _: float, trivia: Trivia, raw: str) -> None: 

694 super().__init__(trivia) 

695 

696 self._raw = raw 

697 self._sign = False 

698 

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

700 self._sign = True 

701 

702 def unwrap(self) -> float: 

703 return float(self) 

704 

705 @property 

706 def discriminant(self) -> int: 

707 return 3 

708 

709 @property 

710 def value(self) -> float: 

711 """The wrapped float value""" 

712 return self 

713 

714 def as_string(self) -> str: 

715 return self._raw 

716 

717 def __add__(self, other): 

718 result = super().__add__(other) 

719 

720 return self._new(result) 

721 

722 def __radd__(self, other): 

723 result = super().__radd__(other) 

724 

725 if isinstance(other, Float): 

726 return self._new(result) 

727 

728 return result 

729 

730 def __sub__(self, other): 

731 result = super().__sub__(other) 

732 

733 return self._new(result) 

734 

735 def __rsub__(self, other): 

736 result = super().__rsub__(other) 

737 

738 if isinstance(other, Float): 

739 return self._new(result) 

740 

741 return result 

742 

743 def _new(self, result): 

744 raw = str(result) 

745 

746 if self._sign: 

747 sign = "+" if result >= 0 else "-" 

748 raw = sign + raw 

749 

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

751 

752 def _getstate(self, protocol=3): 

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

754 

755 

756class Bool(Item): 

757 """ 

758 A boolean literal. 

759 """ 

760 

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

762 super().__init__(trivia) 

763 

764 self._value = bool(t) 

765 

766 def unwrap(self) -> bool: 

767 return bool(self) 

768 

769 @property 

770 def discriminant(self) -> int: 

771 return 4 

772 

773 @property 

774 def value(self) -> bool: 

775 """The wrapped boolean value""" 

776 return self._value 

777 

778 def as_string(self) -> str: 

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

780 

781 def _getstate(self, protocol=3): 

782 return self._value, self._trivia 

783 

784 def __bool__(self): 

785 return self._value 

786 

787 __nonzero__ = __bool__ 

788 

789 def __eq__(self, other): 

790 if not isinstance(other, bool): 

791 return NotImplemented 

792 

793 return other == self._value 

794 

795 def __hash__(self): 

796 return hash(self._value) 

797 

798 def __repr__(self): 

799 return repr(self._value) 

800 

801 

802class DateTime(Item, datetime): 

803 """ 

804 A datetime literal. 

805 """ 

806 

807 def __new__( 

808 cls, 

809 year: int, 

810 month: int, 

811 day: int, 

812 hour: int, 

813 minute: int, 

814 second: int, 

815 microsecond: int, 

816 tzinfo: Optional[tzinfo], 

817 *_: Any, 

818 **kwargs: Any, 

819 ) -> datetime: 

820 return datetime.__new__( 

821 cls, 

822 year, 

823 month, 

824 day, 

825 hour, 

826 minute, 

827 second, 

828 microsecond, 

829 tzinfo=tzinfo, 

830 **kwargs, 

831 ) 

832 

833 def __init__( 

834 self, 

835 year: int, 

836 month: int, 

837 day: int, 

838 hour: int, 

839 minute: int, 

840 second: int, 

841 microsecond: int, 

842 tzinfo: Optional[tzinfo], 

843 trivia: Optional[Trivia] = None, 

844 raw: Optional[str] = None, 

845 **kwargs: Any, 

846 ) -> None: 

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

848 

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

850 

851 def unwrap(self) -> datetime: 

852 ( 

853 year, 

854 month, 

855 day, 

856 hour, 

857 minute, 

858 second, 

859 microsecond, 

860 tzinfo, 

861 _, 

862 _, 

863 ) = self._getstate() 

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

865 

866 @property 

867 def discriminant(self) -> int: 

868 return 5 

869 

870 @property 

871 def value(self) -> datetime: 

872 return self 

873 

874 def as_string(self) -> str: 

875 return self._raw 

876 

877 def __add__(self, other): 

878 if PY38: 

879 result = datetime( 

880 self.year, 

881 self.month, 

882 self.day, 

883 self.hour, 

884 self.minute, 

885 self.second, 

886 self.microsecond, 

887 self.tzinfo, 

888 ).__add__(other) 

889 else: 

890 result = super().__add__(other) 

891 

892 return self._new(result) 

893 

894 def __sub__(self, other): 

895 if PY38: 

896 result = datetime( 

897 self.year, 

898 self.month, 

899 self.day, 

900 self.hour, 

901 self.minute, 

902 self.second, 

903 self.microsecond, 

904 self.tzinfo, 

905 ).__sub__(other) 

906 else: 

907 result = super().__sub__(other) 

908 

909 if isinstance(result, datetime): 

910 result = self._new(result) 

911 

912 return result 

913 

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

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

916 

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

918 result = super().astimezone(tz) 

919 if PY38: 

920 return result 

921 return self._new(result) 

922 

923 def _new(self, result) -> "DateTime": 

924 raw = result.isoformat() 

925 

926 return DateTime( 

927 result.year, 

928 result.month, 

929 result.day, 

930 result.hour, 

931 result.minute, 

932 result.second, 

933 result.microsecond, 

934 result.tzinfo, 

935 self._trivia, 

936 raw, 

937 ) 

938 

939 def _getstate(self, protocol=3): 

940 return ( 

941 self.year, 

942 self.month, 

943 self.day, 

944 self.hour, 

945 self.minute, 

946 self.second, 

947 self.microsecond, 

948 self.tzinfo, 

949 self._trivia, 

950 self._raw, 

951 ) 

952 

953 

954class Date(Item, date): 

955 """ 

956 A date literal. 

957 """ 

958 

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

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

961 

962 def __init__( 

963 self, year: int, month: int, day: int, trivia: Trivia, raw: str 

964 ) -> None: 

965 super().__init__(trivia) 

966 

967 self._raw = raw 

968 

969 def unwrap(self) -> date: 

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

971 return date(year, month, day) 

972 

973 @property 

974 def discriminant(self) -> int: 

975 return 6 

976 

977 @property 

978 def value(self) -> date: 

979 return self 

980 

981 def as_string(self) -> str: 

982 return self._raw 

983 

984 def __add__(self, other): 

985 if PY38: 

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

987 else: 

988 result = super().__add__(other) 

989 

990 return self._new(result) 

991 

992 def __sub__(self, other): 

993 if PY38: 

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

995 else: 

996 result = super().__sub__(other) 

997 

998 if isinstance(result, date): 

999 result = self._new(result) 

1000 

1001 return result 

1002 

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

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

1005 

1006 def _new(self, result): 

1007 raw = result.isoformat() 

1008 

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

1010 

1011 def _getstate(self, protocol=3): 

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

1013 

1014 

1015class Time(Item, time): 

1016 """ 

1017 A time literal. 

1018 """ 

1019 

1020 def __new__( 

1021 cls, 

1022 hour: int, 

1023 minute: int, 

1024 second: int, 

1025 microsecond: int, 

1026 tzinfo: Optional[tzinfo], 

1027 *_: Any, 

1028 ) -> time: 

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

1030 

1031 def __init__( 

1032 self, 

1033 hour: int, 

1034 minute: int, 

1035 second: int, 

1036 microsecond: int, 

1037 tzinfo: Optional[tzinfo], 

1038 trivia: Trivia, 

1039 raw: str, 

1040 ) -> None: 

1041 super().__init__(trivia) 

1042 

1043 self._raw = raw 

1044 

1045 def unwrap(self) -> time: 

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

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

1048 

1049 @property 

1050 def discriminant(self) -> int: 

1051 return 7 

1052 

1053 @property 

1054 def value(self) -> time: 

1055 return self 

1056 

1057 def as_string(self) -> str: 

1058 return self._raw 

1059 

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

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

1062 

1063 def _new(self, result): 

1064 raw = result.isoformat() 

1065 

1066 return Time( 

1067 result.hour, 

1068 result.minute, 

1069 result.second, 

1070 result.microsecond, 

1071 result.tzinfo, 

1072 self._trivia, 

1073 raw, 

1074 ) 

1075 

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

1077 return ( 

1078 self.hour, 

1079 self.minute, 

1080 self.second, 

1081 self.microsecond, 

1082 self.tzinfo, 

1083 self._trivia, 

1084 self._raw, 

1085 ) 

1086 

1087 

1088class _ArrayItemGroup: 

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

1090 

1091 def __init__( 

1092 self, 

1093 value: Optional[Item] = None, 

1094 indent: Optional[Whitespace] = None, 

1095 comma: Optional[Whitespace] = None, 

1096 comment: Optional[Comment] = None, 

1097 ) -> None: 

1098 self.value = value 

1099 self.indent = indent 

1100 self.comma = comma 

1101 self.comment = comment 

1102 

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

1104 return filter( 

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

1106 ) 

1107 

1108 def __repr__(self) -> str: 

1109 return repr(tuple(self)) 

1110 

1111 def is_whitespace(self) -> bool: 

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

1113 

1114 def __bool__(self) -> bool: 

1115 try: 

1116 next(iter(self)) 

1117 except StopIteration: 

1118 return False 

1119 return True 

1120 

1121 

1122class Array(Item, _CustomList): 

1123 """ 

1124 An array literal 

1125 """ 

1126 

1127 def __init__( 

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

1129 ) -> None: 

1130 super().__init__(trivia) 

1131 list.__init__( 

1132 self, 

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

1134 ) 

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

1136 self._value = self._group_values(value) 

1137 self._multiline = multiline 

1138 self._reindex() 

1139 

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

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

1142 groups = [] 

1143 this_group = _ArrayItemGroup() 

1144 for item in value: 

1145 if isinstance(item, Whitespace): 

1146 if "," not in item.s: 

1147 groups.append(this_group) 

1148 this_group = _ArrayItemGroup(indent=item) 

1149 else: 

1150 if this_group.value is None: 

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

1152 this_group.value = Null() 

1153 this_group.comma = item 

1154 elif isinstance(item, Comment): 

1155 if this_group.value is None: 

1156 this_group.value = Null() 

1157 this_group.comment = item 

1158 elif this_group.value is None: 

1159 this_group.value = item 

1160 else: 

1161 groups.append(this_group) 

1162 this_group = _ArrayItemGroup(value=item) 

1163 groups.append(this_group) 

1164 return [group for group in groups if group] 

1165 

1166 def unwrap(self) -> List[Any]: 

1167 unwrapped = [] 

1168 for v in self: 

1169 if hasattr(v, "unwrap"): 

1170 unwrapped.append(v.unwrap()) 

1171 else: 

1172 unwrapped.append(v) 

1173 return unwrapped 

1174 

1175 @property 

1176 def discriminant(self) -> int: 

1177 return 8 

1178 

1179 @property 

1180 def value(self) -> list: 

1181 return self 

1182 

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

1184 for v in self._value: 

1185 yield from v 

1186 

1187 def multiline(self, multiline: bool) -> "Array": 

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

1189 

1190 :Example: 

1191 

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

1193 >>> print(a.as_string()) 

1194 [1, 2, 3] 

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

1196 [ 

1197 1, 

1198 2, 

1199 3, 

1200 ] 

1201 """ 

1202 self._multiline = multiline 

1203 

1204 return self 

1205 

1206 def as_string(self) -> str: 

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

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

1209 

1210 s = "[\n" 

1211 s += "".join( 

1212 self.trivia.indent 

1213 + " " * 4 

1214 + v.value.as_string() 

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

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

1217 + "\n" 

1218 for v in self._value 

1219 if v.value is not None 

1220 ) 

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

1222 

1223 return s 

1224 

1225 def _reindex(self) -> None: 

1226 self._index_map.clear() 

1227 index = 0 

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

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

1230 continue 

1231 self._index_map[index] = i 

1232 index += 1 

1233 

1234 def add_line( 

1235 self, 

1236 *items: Any, 

1237 indent: str = " ", 

1238 comment: Optional[str] = None, 

1239 add_comma: bool = True, 

1240 newline: bool = True, 

1241 ) -> None: 

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

1243 When add_comma is True, only accept actual values and 

1244 ", " will be added between values automatically. 

1245 

1246 :Example: 

1247 

1248 >>> a = array() 

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

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

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

1252 >>> print(a.as_string()) 

1253 [ 

1254 1, 2, 3, 

1255 4, 5, 6, 

1256 ] 

1257 """ 

1258 new_values: List[Item] = [] 

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

1260 if first_indent: 

1261 new_values.append(Whitespace(first_indent)) 

1262 whitespace = "" 

1263 data_values = [] 

1264 for i, el in enumerate(items): 

1265 it = item(el, _parent=self) 

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

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

1268 if not isinstance(it, Whitespace): 

1269 if whitespace: 

1270 new_values.append(Whitespace(whitespace)) 

1271 whitespace = "" 

1272 new_values.append(it) 

1273 data_values.append(it.value) 

1274 if add_comma: 

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

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

1277 new_values.append(Whitespace(" ")) 

1278 elif "," not in it.s: 

1279 whitespace += it.s 

1280 else: 

1281 new_values.append(it) 

1282 if whitespace: 

1283 new_values.append(Whitespace(whitespace)) 

1284 if comment: 

1285 indent = " " if items else "" 

1286 new_values.append( 

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

1288 ) 

1289 list.extend(self, data_values) 

1290 if len(self._value) > 0: 

1291 last_item = self._value[-1] 

1292 last_value_item = next( 

1293 ( 

1294 v 

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

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

1297 ), 

1298 None, 

1299 ) 

1300 if last_value_item is not None: 

1301 last_value_item.comma = Whitespace(",") 

1302 if last_item.is_whitespace(): 

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

1304 else: 

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

1306 else: 

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

1308 self._reindex() 

1309 

1310 def clear(self) -> None: 

1311 """Clear the array.""" 

1312 list.clear(self) 

1313 self._index_map.clear() 

1314 self._value.clear() 

1315 

1316 def __len__(self) -> int: 

1317 return list.__len__(self) 

1318 

1319 def __getitem__(self, key: Union[int, slice]) -> Any: 

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

1321 if rv.is_boolean(): 

1322 return bool(rv) 

1323 return rv 

1324 

1325 def __setitem__(self, key: Union[int, slice], value: Any) -> Any: 

1326 it = item(value, _parent=self) 

1327 list.__setitem__(self, key, it) 

1328 if isinstance(key, slice): 

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

1330 if key < 0: 

1331 key += len(self) 

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

1333 

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

1335 it = item(value, _parent=self) 

1336 length = len(self) 

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

1338 list.insert(self, pos, it) 

1339 if pos < 0: 

1340 pos += length 

1341 if pos < 0: 

1342 pos = 0 

1343 

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

1345 default_indent = " " 

1346 if pos < length: 

1347 try: 

1348 idx = self._index_map[pos] 

1349 except KeyError as e: 

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

1351 else: 

1352 idx = len(self._value) 

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

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

1355 idx -= 1 

1356 if ( 

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

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

1359 ): 

1360 default_indent = "\n " 

1361 indent: Optional[Item] = None 

1362 comma: Optional[Item] = Whitespace(",") if pos < length else None 

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

1364 # Prefer to copy the indentation from the item after 

1365 indent = self._value[idx].indent 

1366 if idx > 0: 

1367 last_item = self._value[idx - 1] 

1368 if indent is None: 

1369 indent = last_item.indent 

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

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

1372 # 2) the array is multiline 

1373 comma = last_item.comma 

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

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

1376 last_item.comma = Whitespace(",") 

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

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

1379 indent = Whitespace(default_indent) 

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

1381 self._value.insert(idx, new_item) 

1382 self._reindex() 

1383 

1384 def __delitem__(self, key: Union[int, slice]): 

1385 length = len(self) 

1386 list.__delitem__(self, key) 

1387 

1388 if isinstance(key, slice): 

1389 indices_to_remove = list( 

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

1391 ) 

1392 else: 

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

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

1395 try: 

1396 idx = self._index_map[i] 

1397 except KeyError as e: 

1398 if not isinstance(key, slice): 

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

1400 else: 

1401 del self._value[idx] 

1402 if ( 

1403 idx == 0 

1404 and len(self._value) > 0 

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

1406 ): 

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

1408 self._value[idx].indent = None 

1409 if len(self._value) > 0: 

1410 v = self._value[-1] 

1411 if not v.is_whitespace(): 

1412 # remove the comma of the last item 

1413 v.comma = None 

1414 

1415 self._reindex() 

1416 

1417 def __str__(self): 

1418 return str([v.value.value for v in self._iter_items() if v.value is not None]) 

1419 

1420 def _getstate(self, protocol=3): 

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

1422 

1423 

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

1425 

1426 

1427class AbstractTable(Item, _CustomDict): 

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

1429 

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

1431 Item.__init__(self, trivia) 

1432 

1433 self._value = value 

1434 

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

1436 if k is not None: 

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

1438 

1439 def unwrap(self) -> Dict[str, Any]: 

1440 unwrapped = {} 

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

1442 if isinstance(k, Key): 

1443 k = k.key 

1444 if hasattr(v, "unwrap"): 

1445 v = v.unwrap() 

1446 unwrapped[k] = v 

1447 

1448 return unwrapped 

1449 

1450 @property 

1451 def value(self) -> "container.Container": 

1452 return self._value 

1453 

1454 @overload 

1455 def append(self: AT, key: None, value: Union[Comment, Whitespace]) -> AT: 

1456 ... 

1457 

1458 @overload 

1459 def append(self: AT, key: Union[Key, str], value: Any) -> AT: 

1460 ... 

1461 

1462 def append(self, key, value): 

1463 raise NotImplementedError 

1464 

1465 @overload 

1466 def add(self: AT, value: Union[Comment, Whitespace]) -> AT: 

1467 ... 

1468 

1469 @overload 

1470 def add(self: AT, key: Union[Key, str], value: Any) -> AT: 

1471 ... 

1472 

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

1474 if value is None: 

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

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

1477 raise ValueError(msg) 

1478 

1479 key, value = None, key 

1480 

1481 return self.append(key, value) 

1482 

1483 def remove(self: AT, key: Union[Key, str]) -> AT: 

1484 self._value.remove(key) 

1485 

1486 if isinstance(key, Key): 

1487 key = key.key 

1488 

1489 if key is not None: 

1490 dict.__delitem__(self, key) 

1491 

1492 return self 

1493 

1494 def setdefault(self, key: Union[Key, str], default: Any) -> Any: 

1495 super().setdefault(key, default) 

1496 return self[key] 

1497 

1498 def __str__(self): 

1499 return str(self.value) 

1500 

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

1502 return copy.copy(self) 

1503 

1504 def __repr__(self) -> str: 

1505 return repr(self.value) 

1506 

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

1508 return iter(self._value) 

1509 

1510 def __len__(self) -> int: 

1511 return len(self._value) 

1512 

1513 def __delitem__(self, key: Union[Key, str]) -> None: 

1514 self.remove(key) 

1515 

1516 def __getitem__(self, key: Union[Key, str]) -> Item: 

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

1518 

1519 def __setitem__(self, key: Union[Key, str], value: Any) -> None: 

1520 if not isinstance(value, Item): 

1521 value = item(value, _parent=self) 

1522 

1523 is_replace = key in self 

1524 self._value[key] = value 

1525 

1526 if key is not None: 

1527 dict.__setitem__(self, key, value) 

1528 

1529 if is_replace: 

1530 return 

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

1532 if not m: 

1533 return 

1534 

1535 indent = m.group(1) 

1536 

1537 if not isinstance(value, Whitespace): 

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

1539 if not m: 

1540 value.trivia.indent = indent 

1541 else: 

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

1543 

1544 

1545class Table(AbstractTable): 

1546 """ 

1547 A table literal. 

1548 """ 

1549 

1550 def __init__( 

1551 self, 

1552 value: "container.Container", 

1553 trivia: Trivia, 

1554 is_aot_element: bool, 

1555 is_super_table: Optional[bool] = None, 

1556 name: Optional[str] = None, 

1557 display_name: Optional[str] = None, 

1558 ) -> None: 

1559 super().__init__(value, trivia) 

1560 

1561 self.name = name 

1562 self.display_name = display_name 

1563 self._is_aot_element = is_aot_element 

1564 self._is_super_table = is_super_table 

1565 

1566 @property 

1567 def discriminant(self) -> int: 

1568 return 9 

1569 

1570 def __copy__(self) -> "Table": 

1571 return type(self)( 

1572 self._value.copy(), 

1573 self._trivia.copy(), 

1574 self._is_aot_element, 

1575 self._is_super_table, 

1576 self.name, 

1577 self.display_name, 

1578 ) 

1579 

1580 def append(self, key, _item): 

1581 """ 

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

1583 """ 

1584 if not isinstance(_item, Item): 

1585 _item = item(_item, _parent=self) 

1586 

1587 self._value.append(key, _item) 

1588 

1589 if isinstance(key, Key): 

1590 key = next(iter(key)).key 

1591 _item = self._value[key] 

1592 

1593 if key is not None: 

1594 dict.__setitem__(self, key, _item) 

1595 

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

1597 if not m: 

1598 return self 

1599 

1600 indent = m.group(1) 

1601 

1602 if not isinstance(_item, Whitespace): 

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

1604 if not m: 

1605 _item.trivia.indent = indent 

1606 else: 

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

1608 

1609 return self 

1610 

1611 def raw_append(self, key: Union[Key, str], _item: Any) -> "Table": 

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

1613 if not isinstance(_item, Item): 

1614 _item = item(_item) 

1615 

1616 self._value.append(key, _item) 

1617 

1618 if isinstance(key, Key): 

1619 key = next(iter(key)).key 

1620 _item = self._value[key] 

1621 

1622 if key is not None: 

1623 dict.__setitem__(self, key, _item) 

1624 

1625 return self 

1626 

1627 def is_aot_element(self) -> bool: 

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

1629 return self._is_aot_element 

1630 

1631 def is_super_table(self) -> bool: 

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

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

1634 if self._is_super_table is not None: 

1635 return self._is_super_table 

1636 # If the table has only one child and that child is a table, then it is a super table. 

1637 if len(self) != 1: 

1638 return False 

1639 only_child = next(iter(self.values())) 

1640 return isinstance(only_child, (Table, AoT)) 

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 self.display_name = None 

1665 

1666 for child in self.values(): 

1667 if hasattr(child, "invalidate_display_name"): 

1668 child.invalidate_display_name() 

1669 

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

1671 return ( 

1672 self._value, 

1673 self._trivia, 

1674 self._is_aot_element, 

1675 self._is_super_table, 

1676 self.name, 

1677 self.display_name, 

1678 ) 

1679 

1680 

1681class InlineTable(AbstractTable): 

1682 """ 

1683 An inline table literal. 

1684 """ 

1685 

1686 def __init__( 

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

1688 ) -> None: 

1689 super().__init__(value, trivia) 

1690 

1691 self._new = new 

1692 

1693 @property 

1694 def discriminant(self) -> int: 

1695 return 10 

1696 

1697 def append(self, key, _item): 

1698 """ 

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

1700 """ 

1701 if not isinstance(_item, Item): 

1702 _item = item(_item, _parent=self) 

1703 

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

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

1706 _item.trivia.indent = " " 

1707 if _item.trivia.comment: 

1708 _item.trivia.comment = "" 

1709 

1710 self._value.append(key, _item) 

1711 

1712 if isinstance(key, Key): 

1713 key = key.key 

1714 

1715 if key is not None: 

1716 dict.__setitem__(self, key, _item) 

1717 

1718 return self 

1719 

1720 def as_string(self) -> str: 

1721 buf = "{" 

1722 last_item_idx = next( 

1723 ( 

1724 i 

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

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

1727 ), 

1728 None, 

1729 ) 

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

1731 if k is None: 

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

1733 if self._new: 

1734 buf = buf.rstrip(", ") 

1735 else: 

1736 buf = buf.rstrip(",") 

1737 

1738 buf += v.as_string() 

1739 

1740 continue 

1741 

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

1743 buf += ( 

1744 f"{v.trivia.indent}" 

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

1746 f"{k.sep}" 

1747 f"{v.as_string()}" 

1748 f"{v.trivia.comment}" 

1749 f"{v_trivia_trail}" 

1750 ) 

1751 

1752 if last_item_idx is not None and i < last_item_idx: 

1753 buf += "," 

1754 if self._new: 

1755 buf += " " 

1756 

1757 buf += "}" 

1758 

1759 return buf 

1760 

1761 def __setitem__(self, key: Union[Key, str], value: Any) -> None: 

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

1763 value.trivia.comment = "" 

1764 super().__setitem__(key, value) 

1765 

1766 def __copy__(self) -> "InlineTable": 

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

1768 

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

1770 return (self._value, self._trivia) 

1771 

1772 

1773class String(str, Item): 

1774 """ 

1775 A string literal. 

1776 """ 

1777 

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

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

1780 

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

1782 super().__init__(trivia) 

1783 

1784 self._t = t 

1785 self._original = original 

1786 

1787 def unwrap(self) -> str: 

1788 return str(self) 

1789 

1790 @property 

1791 def discriminant(self) -> int: 

1792 return 11 

1793 

1794 @property 

1795 def value(self) -> str: 

1796 return self 

1797 

1798 def as_string(self) -> str: 

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

1800 

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

1802 if not isinstance(other, str): 

1803 return NotImplemented 

1804 result = super().__add__(other) 

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

1806 

1807 return self._new(result, original) 

1808 

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

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

1811 

1812 def _getstate(self, protocol=3): 

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

1814 

1815 @classmethod 

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

1817 value = decode(value) 

1818 

1819 invalid = type_.invalid_sequences 

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

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

1822 

1823 escaped = type_.escaped_sequences 

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

1825 

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

1827 

1828 

1829class AoT(Item, _CustomList): 

1830 """ 

1831 An array of table literal 

1832 """ 

1833 

1834 def __init__( 

1835 self, body: List[Table], name: Optional[str] = None, parsed: bool = False 

1836 ) -> None: 

1837 self.name = name 

1838 self._body: List[Table] = [] 

1839 self._parsed = parsed 

1840 

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

1842 

1843 for table in body: 

1844 self.append(table) 

1845 

1846 def unwrap(self) -> List[Dict[str, Any]]: 

1847 unwrapped = [] 

1848 for t in self._body: 

1849 if hasattr(t, "unwrap"): 

1850 unwrapped.append(t.unwrap()) 

1851 else: 

1852 unwrapped.append(t) 

1853 return unwrapped 

1854 

1855 @property 

1856 def body(self) -> List[Table]: 

1857 return self._body 

1858 

1859 @property 

1860 def discriminant(self) -> int: 

1861 return 12 

1862 

1863 @property 

1864 def value(self) -> List[Dict[Any, Any]]: 

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

1866 

1867 def __len__(self) -> int: 

1868 return len(self._body) 

1869 

1870 @overload 

1871 def __getitem__(self, key: slice) -> List[Table]: 

1872 ... 

1873 

1874 @overload 

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

1876 ... 

1877 

1878 def __getitem__(self, key): 

1879 return self._body[key] 

1880 

1881 def __setitem__(self, key: Union[slice, int], value: Any) -> None: 

1882 raise NotImplementedError 

1883 

1884 def __delitem__(self, key: Union[slice, int]) -> None: 

1885 del self._body[key] 

1886 list.__delitem__(self, key) 

1887 

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

1889 value = item(value, _parent=self) 

1890 if not isinstance(value, Table): 

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

1892 length = len(self) 

1893 if index < 0: 

1894 index += length 

1895 if index < 0: 

1896 index = 0 

1897 elif index >= length: 

1898 index = length 

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

1900 if m: 

1901 indent = m.group(1) 

1902 

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

1904 if not m: 

1905 value.trivia.indent = indent 

1906 else: 

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

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

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

1910 if not self._parsed: 

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

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

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

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

1915 self._body.insert(index, value) 

1916 list.insert(self, index, value) 

1917 

1918 def invalidate_display_name(self): 

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

1920 for child in self: 

1921 if hasattr(child, "invalidate_display_name"): 

1922 child.invalidate_display_name() 

1923 

1924 def as_string(self) -> str: 

1925 b = "" 

1926 for table in self._body: 

1927 b += table.as_string() 

1928 

1929 return b 

1930 

1931 def __repr__(self) -> str: 

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

1933 

1934 def _getstate(self, protocol=3): 

1935 return self._body, self.name, self._parsed 

1936 

1937 

1938class Null(Item): 

1939 """ 

1940 A null item. 

1941 """ 

1942 

1943 def __init__(self) -> None: 

1944 pass 

1945 

1946 def unwrap(self) -> None: 

1947 return None 

1948 

1949 @property 

1950 def discriminant(self) -> int: 

1951 return -1 

1952 

1953 @property 

1954 def value(self) -> None: 

1955 return None 

1956 

1957 def as_string(self) -> str: 

1958 return "" 

1959 

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

1961 return ()