items.py 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955
  1. from __future__ import annotations
  2. import abc
  3. import copy
  4. import dataclasses
  5. import math
  6. import re
  7. import string
  8. import sys
  9. from datetime import date
  10. from datetime import datetime
  11. from datetime import time
  12. from datetime import tzinfo
  13. from enum import Enum
  14. from typing import TYPE_CHECKING
  15. from typing import Any
  16. from typing import Callable
  17. from typing import Collection
  18. from typing import Iterable
  19. from typing import Iterator
  20. from typing import Sequence
  21. from typing import TypeVar
  22. from typing import cast
  23. from typing import overload
  24. from tomlkit._compat import PY38
  25. from tomlkit._compat import decode
  26. from tomlkit._types import _CustomDict
  27. from tomlkit._types import _CustomFloat
  28. from tomlkit._types import _CustomInt
  29. from tomlkit._types import _CustomList
  30. from tomlkit._types import wrap_method
  31. from tomlkit._utils import CONTROL_CHARS
  32. from tomlkit._utils import escape_string
  33. from tomlkit.exceptions import InvalidStringError
  34. if TYPE_CHECKING:
  35. from tomlkit import container
  36. ItemT = TypeVar("ItemT", bound="Item")
  37. Encoder = Callable[[Any], "Item"]
  38. CUSTOM_ENCODERS: list[Encoder] = []
  39. AT = TypeVar("AT", bound="AbstractTable")
  40. class _ConvertError(TypeError, ValueError):
  41. """An internal error raised when item() fails to convert a value.
  42. It should be a TypeError, but due to historical reasons
  43. it needs to subclass ValueError as well.
  44. """
  45. @overload
  46. def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool:
  47. ...
  48. @overload
  49. def item(value: int, _parent: Item | None = ..., _sort_keys: bool = ...) -> Integer:
  50. ...
  51. @overload
  52. def item(value: float, _parent: Item | None = ..., _sort_keys: bool = ...) -> Float:
  53. ...
  54. @overload
  55. def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> String:
  56. ...
  57. @overload
  58. def item(
  59. value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...
  60. ) -> DateTime:
  61. ...
  62. @overload
  63. def item(value: date, _parent: Item | None = ..., _sort_keys: bool = ...) -> Date:
  64. ...
  65. @overload
  66. def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Time:
  67. ...
  68. @overload
  69. def item(
  70. value: Sequence[dict], _parent: Item | None = ..., _sort_keys: bool = ...
  71. ) -> AoT:
  72. ...
  73. @overload
  74. def item(value: Sequence, _parent: Item | None = ..., _sort_keys: bool = ...) -> Array:
  75. ...
  76. @overload
  77. def item(value: dict, _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable:
  78. ...
  79. @overload
  80. def item(value: dict, _parent: Item | None = ..., _sort_keys: bool = ...) -> Table:
  81. ...
  82. @overload
  83. def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT:
  84. ...
  85. def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item:
  86. """Create a TOML item from a Python object.
  87. :Example:
  88. >>> item(42)
  89. 42
  90. >>> item([1, 2, 3])
  91. [1, 2, 3]
  92. >>> item({'a': 1, 'b': 2})
  93. a = 1
  94. b = 2
  95. """
  96. from tomlkit.container import Container
  97. if isinstance(value, Item):
  98. return value
  99. if isinstance(value, bool):
  100. return Bool(value, Trivia())
  101. elif isinstance(value, int):
  102. return Integer(value, Trivia(), str(value))
  103. elif isinstance(value, float):
  104. return Float(value, Trivia(), str(value))
  105. elif isinstance(value, dict):
  106. table_constructor = (
  107. InlineTable if isinstance(_parent, (Array, InlineTable)) else Table
  108. )
  109. val = table_constructor(Container(), Trivia(), False)
  110. for k, v in sorted(
  111. value.items(),
  112. key=lambda i: (isinstance(i[1], dict), i[0]) if _sort_keys else 1,
  113. ):
  114. val[k] = item(v, _parent=val, _sort_keys=_sort_keys)
  115. return val
  116. elif isinstance(value, (list, tuple)):
  117. if (
  118. value
  119. and all(isinstance(v, dict) for v in value)
  120. and (_parent is None or isinstance(_parent, Table))
  121. ):
  122. a = AoT([])
  123. table_constructor = Table
  124. else:
  125. a = Array([], Trivia())
  126. table_constructor = InlineTable
  127. for v in value:
  128. if isinstance(v, dict):
  129. table = table_constructor(Container(), Trivia(), True)
  130. for k, _v in sorted(
  131. v.items(),
  132. key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
  133. ):
  134. i = item(_v, _parent=table, _sort_keys=_sort_keys)
  135. if isinstance(table, InlineTable):
  136. i.trivia.trail = ""
  137. table[k] = i
  138. v = table
  139. a.append(v)
  140. return a
  141. elif isinstance(value, str):
  142. return String.from_raw(value)
  143. elif isinstance(value, datetime):
  144. return DateTime(
  145. value.year,
  146. value.month,
  147. value.day,
  148. value.hour,
  149. value.minute,
  150. value.second,
  151. value.microsecond,
  152. value.tzinfo,
  153. Trivia(),
  154. value.isoformat().replace("+00:00", "Z"),
  155. )
  156. elif isinstance(value, date):
  157. return Date(value.year, value.month, value.day, Trivia(), value.isoformat())
  158. elif isinstance(value, time):
  159. return Time(
  160. value.hour,
  161. value.minute,
  162. value.second,
  163. value.microsecond,
  164. value.tzinfo,
  165. Trivia(),
  166. value.isoformat(),
  167. )
  168. else:
  169. for encoder in CUSTOM_ENCODERS:
  170. try:
  171. rv = encoder(value)
  172. except TypeError:
  173. pass
  174. else:
  175. if not isinstance(rv, Item):
  176. raise _ConvertError(
  177. f"Custom encoder returned {type(rv)}, not a subclass of Item"
  178. )
  179. return rv
  180. raise _ConvertError(f"Invalid type {type(value)}")
  181. class StringType(Enum):
  182. # Single Line Basic
  183. SLB = '"'
  184. # Multi Line Basic
  185. MLB = '"""'
  186. # Single Line Literal
  187. SLL = "'"
  188. # Multi Line Literal
  189. MLL = "'''"
  190. @classmethod
  191. def select(cls, literal=False, multiline=False) -> StringType:
  192. return {
  193. (False, False): cls.SLB,
  194. (False, True): cls.MLB,
  195. (True, False): cls.SLL,
  196. (True, True): cls.MLL,
  197. }[(literal, multiline)]
  198. @property
  199. def escaped_sequences(self) -> Collection[str]:
  200. # https://toml.io/en/v1.0.0#string
  201. escaped_in_basic = CONTROL_CHARS | {"\\"}
  202. allowed_in_multiline = {"\n", "\r"}
  203. return {
  204. StringType.SLB: escaped_in_basic | {'"'},
  205. StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline,
  206. StringType.SLL: (),
  207. StringType.MLL: (),
  208. }[self]
  209. @property
  210. def invalid_sequences(self) -> Collection[str]:
  211. # https://toml.io/en/v1.0.0#string
  212. forbidden_in_literal = CONTROL_CHARS - {"\t"}
  213. allowed_in_multiline = {"\n", "\r"}
  214. return {
  215. StringType.SLB: (),
  216. StringType.MLB: (),
  217. StringType.SLL: forbidden_in_literal | {"'"},
  218. StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline,
  219. }[self]
  220. @property
  221. def unit(self) -> str:
  222. return self.value[0]
  223. def is_basic(self) -> bool:
  224. return self in {StringType.SLB, StringType.MLB}
  225. def is_literal(self) -> bool:
  226. return self in {StringType.SLL, StringType.MLL}
  227. def is_singleline(self) -> bool:
  228. return self in {StringType.SLB, StringType.SLL}
  229. def is_multiline(self) -> bool:
  230. return self in {StringType.MLB, StringType.MLL}
  231. def toggle(self) -> StringType:
  232. return {
  233. StringType.SLB: StringType.MLB,
  234. StringType.MLB: StringType.SLB,
  235. StringType.SLL: StringType.MLL,
  236. StringType.MLL: StringType.SLL,
  237. }[self]
  238. class BoolType(Enum):
  239. TRUE = "true"
  240. FALSE = "false"
  241. def __bool__(self):
  242. return {BoolType.TRUE: True, BoolType.FALSE: False}[self]
  243. def __iter__(self):
  244. return iter(self.value)
  245. def __len__(self):
  246. return len(self.value)
  247. @dataclasses.dataclass
  248. class Trivia:
  249. """
  250. Trivia information (aka metadata).
  251. """
  252. # Whitespace before a value.
  253. indent: str = ""
  254. # Whitespace after a value, but before a comment.
  255. comment_ws: str = ""
  256. # Comment, starting with # character, or empty string if no comment.
  257. comment: str = ""
  258. # Trailing newline.
  259. trail: str = "\n"
  260. def copy(self) -> Trivia:
  261. return dataclasses.replace(self)
  262. class KeyType(Enum):
  263. """
  264. The type of a Key.
  265. Keys can be bare (unquoted), or quoted using basic ("), or literal (')
  266. quotes following the same escaping rules as single-line StringType.
  267. """
  268. Bare = ""
  269. Basic = '"'
  270. Literal = "'"
  271. class Key(abc.ABC):
  272. """Base class for a key"""
  273. sep: str
  274. _original: str
  275. _keys: list[SingleKey]
  276. _dotted: bool
  277. key: str
  278. @abc.abstractmethod
  279. def __hash__(self) -> int:
  280. pass
  281. @abc.abstractmethod
  282. def __eq__(self, __o: object) -> bool:
  283. pass
  284. def is_dotted(self) -> bool:
  285. """If the key is followed by other keys"""
  286. return self._dotted
  287. def __iter__(self) -> Iterator[SingleKey]:
  288. return iter(self._keys)
  289. def concat(self, other: Key) -> DottedKey:
  290. """Concatenate keys into a dotted key"""
  291. keys = self._keys + other._keys
  292. return DottedKey(keys, sep=self.sep)
  293. def is_multi(self) -> bool:
  294. """Check if the key contains multiple keys"""
  295. return len(self._keys) > 1
  296. def as_string(self) -> str:
  297. """The TOML representation"""
  298. return self._original
  299. def __str__(self) -> str:
  300. return self.as_string()
  301. def __repr__(self) -> str:
  302. return f"<Key {self.as_string()}>"
  303. class SingleKey(Key):
  304. """A single key"""
  305. def __init__(
  306. self,
  307. k: str,
  308. t: KeyType | None = None,
  309. sep: str | None = None,
  310. original: str | None = None,
  311. ) -> None:
  312. if t is None:
  313. if not k or any(
  314. c not in string.ascii_letters + string.digits + "-" + "_" for c in k
  315. ):
  316. t = KeyType.Basic
  317. else:
  318. t = KeyType.Bare
  319. self.t = t
  320. if sep is None:
  321. sep = " = "
  322. self.sep = sep
  323. self.key = k
  324. if original is None:
  325. key_str = escape_string(k) if t == KeyType.Basic else k
  326. original = f"{t.value}{key_str}{t.value}"
  327. self._original = original
  328. self._keys = [self]
  329. self._dotted = False
  330. @property
  331. def delimiter(self) -> str:
  332. """The delimiter: double quote/single quote/none"""
  333. return self.t.value
  334. def is_bare(self) -> bool:
  335. """Check if the key is bare"""
  336. return self.t == KeyType.Bare
  337. def __hash__(self) -> int:
  338. return hash(self.key)
  339. def __eq__(self, other: Any) -> bool:
  340. if isinstance(other, Key):
  341. return isinstance(other, SingleKey) and self.key == other.key
  342. return self.key == other
  343. class DottedKey(Key):
  344. def __init__(
  345. self,
  346. keys: Iterable[SingleKey],
  347. sep: str | None = None,
  348. original: str | None = None,
  349. ) -> None:
  350. self._keys = list(keys)
  351. if original is None:
  352. original = ".".join(k.as_string() for k in self._keys)
  353. self.sep = " = " if sep is None else sep
  354. self._original = original
  355. self._dotted = False
  356. self.key = ".".join(k.key for k in self._keys)
  357. def __hash__(self) -> int:
  358. return hash(tuple(self._keys))
  359. def __eq__(self, __o: object) -> bool:
  360. return isinstance(__o, DottedKey) and self._keys == __o._keys
  361. class Item:
  362. """
  363. An item within a TOML document.
  364. """
  365. def __init__(self, trivia: Trivia) -> None:
  366. self._trivia = trivia
  367. @property
  368. def trivia(self) -> Trivia:
  369. """The trivia element associated with this item"""
  370. return self._trivia
  371. @property
  372. def discriminant(self) -> int:
  373. raise NotImplementedError()
  374. def as_string(self) -> str:
  375. """The TOML representation"""
  376. raise NotImplementedError()
  377. @property
  378. def value(self) -> Any:
  379. return self
  380. def unwrap(self) -> Any:
  381. """Returns as pure python object (ppo)"""
  382. raise NotImplementedError()
  383. # Helpers
  384. def comment(self, comment: str) -> Item:
  385. """Attach a comment to this item"""
  386. if not comment.strip().startswith("#"):
  387. comment = "# " + comment
  388. self._trivia.comment_ws = " "
  389. self._trivia.comment = comment
  390. return self
  391. def indent(self, indent: int) -> Item:
  392. """Indent this item with given number of spaces"""
  393. if self._trivia.indent.startswith("\n"):
  394. self._trivia.indent = "\n" + " " * indent
  395. else:
  396. self._trivia.indent = " " * indent
  397. return self
  398. def is_boolean(self) -> bool:
  399. return isinstance(self, Bool)
  400. def is_table(self) -> bool:
  401. return isinstance(self, Table)
  402. def is_inline_table(self) -> bool:
  403. return isinstance(self, InlineTable)
  404. def is_aot(self) -> bool:
  405. return isinstance(self, AoT)
  406. def _getstate(self, protocol=3):
  407. return (self._trivia,)
  408. def __reduce__(self):
  409. return self.__reduce_ex__(2)
  410. def __reduce_ex__(self, protocol):
  411. return self.__class__, self._getstate(protocol)
  412. class Whitespace(Item):
  413. """
  414. A whitespace literal.
  415. """
  416. def __init__(self, s: str, fixed: bool = False) -> None:
  417. self._s = s
  418. self._fixed = fixed
  419. @property
  420. def s(self) -> str:
  421. return self._s
  422. @property
  423. def value(self) -> str:
  424. """The wrapped string of the whitespace"""
  425. return self._s
  426. @property
  427. def trivia(self) -> Trivia:
  428. raise RuntimeError("Called trivia on a Whitespace variant.")
  429. @property
  430. def discriminant(self) -> int:
  431. return 0
  432. def is_fixed(self) -> bool:
  433. """If the whitespace is fixed, it can't be merged or discarded from the output."""
  434. return self._fixed
  435. def as_string(self) -> str:
  436. return self._s
  437. def __repr__(self) -> str:
  438. return f"<{self.__class__.__name__} {repr(self._s)}>"
  439. def _getstate(self, protocol=3):
  440. return self._s, self._fixed
  441. class Comment(Item):
  442. """
  443. A comment literal.
  444. """
  445. @property
  446. def discriminant(self) -> int:
  447. return 1
  448. def as_string(self) -> str:
  449. return (
  450. f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
  451. )
  452. def __str__(self) -> str:
  453. return f"{self._trivia.indent}{decode(self._trivia.comment)}"
  454. class Integer(Item, _CustomInt):
  455. """
  456. An integer literal.
  457. """
  458. def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
  459. return int.__new__(cls, value)
  460. def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
  461. super().__init__(trivia)
  462. self._original = value
  463. self._raw = raw
  464. self._sign = False
  465. if re.match(r"^[+\-]\d+$", raw):
  466. self._sign = True
  467. def unwrap(self) -> int:
  468. return self._original
  469. __int__ = unwrap
  470. def __hash__(self) -> int:
  471. return hash(self.unwrap())
  472. @property
  473. def discriminant(self) -> int:
  474. return 2
  475. @property
  476. def value(self) -> int:
  477. """The wrapped integer value"""
  478. return self
  479. def as_string(self) -> str:
  480. return self._raw
  481. def _new(self, result):
  482. raw = str(result)
  483. if self._sign:
  484. sign = "+" if result >= 0 else "-"
  485. raw = sign + raw
  486. return Integer(result, self._trivia, raw)
  487. def _getstate(self, protocol=3):
  488. return int(self), self._trivia, self._raw
  489. # int methods
  490. __abs__ = wrap_method(int.__abs__)
  491. __add__ = wrap_method(int.__add__)
  492. __and__ = wrap_method(int.__and__)
  493. __ceil__ = wrap_method(int.__ceil__)
  494. __eq__ = int.__eq__
  495. __floor__ = wrap_method(int.__floor__)
  496. __floordiv__ = wrap_method(int.__floordiv__)
  497. __invert__ = wrap_method(int.__invert__)
  498. __le__ = int.__le__
  499. __lshift__ = wrap_method(int.__lshift__)
  500. __lt__ = int.__lt__
  501. __mod__ = wrap_method(int.__mod__)
  502. __mul__ = wrap_method(int.__mul__)
  503. __neg__ = wrap_method(int.__neg__)
  504. __or__ = wrap_method(int.__or__)
  505. __pos__ = wrap_method(int.__pos__)
  506. __pow__ = wrap_method(int.__pow__)
  507. __radd__ = wrap_method(int.__radd__)
  508. __rand__ = wrap_method(int.__rand__)
  509. __rfloordiv__ = wrap_method(int.__rfloordiv__)
  510. __rlshift__ = wrap_method(int.__rlshift__)
  511. __rmod__ = wrap_method(int.__rmod__)
  512. __rmul__ = wrap_method(int.__rmul__)
  513. __ror__ = wrap_method(int.__ror__)
  514. __round__ = wrap_method(int.__round__)
  515. __rpow__ = wrap_method(int.__rpow__)
  516. __rrshift__ = wrap_method(int.__rrshift__)
  517. __rshift__ = wrap_method(int.__rshift__)
  518. __rtruediv__ = wrap_method(int.__rtruediv__)
  519. __rxor__ = wrap_method(int.__rxor__)
  520. __truediv__ = wrap_method(int.__truediv__)
  521. __trunc__ = wrap_method(int.__trunc__)
  522. __xor__ = wrap_method(int.__xor__)
  523. class Float(Item, _CustomFloat):
  524. """
  525. A float literal.
  526. """
  527. def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
  528. return float.__new__(cls, value)
  529. def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
  530. super().__init__(trivia)
  531. self._original = value
  532. self._raw = raw
  533. self._sign = False
  534. if re.match(r"^[+\-].+$", raw):
  535. self._sign = True
  536. def unwrap(self) -> float:
  537. return self._original
  538. __float__ = unwrap
  539. def __hash__(self) -> int:
  540. return hash(self.unwrap())
  541. @property
  542. def discriminant(self) -> int:
  543. return 3
  544. @property
  545. def value(self) -> float:
  546. """The wrapped float value"""
  547. return self
  548. def as_string(self) -> str:
  549. return self._raw
  550. def _new(self, result):
  551. raw = str(result)
  552. if self._sign:
  553. sign = "+" if result >= 0 else "-"
  554. raw = sign + raw
  555. return Float(result, self._trivia, raw)
  556. def _getstate(self, protocol=3):
  557. return float(self), self._trivia, self._raw
  558. # float methods
  559. __abs__ = wrap_method(float.__abs__)
  560. __add__ = wrap_method(float.__add__)
  561. __eq__ = float.__eq__
  562. __floordiv__ = wrap_method(float.__floordiv__)
  563. __le__ = float.__le__
  564. __lt__ = float.__lt__
  565. __mod__ = wrap_method(float.__mod__)
  566. __mul__ = wrap_method(float.__mul__)
  567. __neg__ = wrap_method(float.__neg__)
  568. __pos__ = wrap_method(float.__pos__)
  569. __pow__ = wrap_method(float.__pow__)
  570. __radd__ = wrap_method(float.__radd__)
  571. __rfloordiv__ = wrap_method(float.__rfloordiv__)
  572. __rmod__ = wrap_method(float.__rmod__)
  573. __rmul__ = wrap_method(float.__rmul__)
  574. __round__ = wrap_method(float.__round__)
  575. __rpow__ = wrap_method(float.__rpow__)
  576. __rtruediv__ = wrap_method(float.__rtruediv__)
  577. __truediv__ = wrap_method(float.__truediv__)
  578. __trunc__ = float.__trunc__
  579. if sys.version_info >= (3, 9):
  580. __ceil__ = float.__ceil__
  581. __floor__ = float.__floor__
  582. else:
  583. __ceil__ = math.ceil
  584. __floor__ = math.floor
  585. class Bool(Item):
  586. """
  587. A boolean literal.
  588. """
  589. def __init__(self, t: int, trivia: Trivia) -> None:
  590. super().__init__(trivia)
  591. self._value = bool(t)
  592. def unwrap(self) -> bool:
  593. return bool(self)
  594. @property
  595. def discriminant(self) -> int:
  596. return 4
  597. @property
  598. def value(self) -> bool:
  599. """The wrapped boolean value"""
  600. return self._value
  601. def as_string(self) -> str:
  602. return str(self._value).lower()
  603. def _getstate(self, protocol=3):
  604. return self._value, self._trivia
  605. def __bool__(self):
  606. return self._value
  607. __nonzero__ = __bool__
  608. def __eq__(self, other):
  609. if not isinstance(other, bool):
  610. return NotImplemented
  611. return other == self._value
  612. def __hash__(self):
  613. return hash(self._value)
  614. def __repr__(self):
  615. return repr(self._value)
  616. class DateTime(Item, datetime):
  617. """
  618. A datetime literal.
  619. """
  620. def __new__(
  621. cls,
  622. year: int,
  623. month: int,
  624. day: int,
  625. hour: int,
  626. minute: int,
  627. second: int,
  628. microsecond: int,
  629. tzinfo: tzinfo | None,
  630. *_: Any,
  631. **kwargs: Any,
  632. ) -> datetime:
  633. return datetime.__new__(
  634. cls,
  635. year,
  636. month,
  637. day,
  638. hour,
  639. minute,
  640. second,
  641. microsecond,
  642. tzinfo=tzinfo,
  643. **kwargs,
  644. )
  645. def __init__(
  646. self,
  647. year: int,
  648. month: int,
  649. day: int,
  650. hour: int,
  651. minute: int,
  652. second: int,
  653. microsecond: int,
  654. tzinfo: tzinfo | None,
  655. trivia: Trivia | None = None,
  656. raw: str | None = None,
  657. **kwargs: Any,
  658. ) -> None:
  659. super().__init__(trivia or Trivia())
  660. self._raw = raw or self.isoformat()
  661. def unwrap(self) -> datetime:
  662. (
  663. year,
  664. month,
  665. day,
  666. hour,
  667. minute,
  668. second,
  669. microsecond,
  670. tzinfo,
  671. _,
  672. _,
  673. ) = self._getstate()
  674. return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
  675. @property
  676. def discriminant(self) -> int:
  677. return 5
  678. @property
  679. def value(self) -> datetime:
  680. return self
  681. def as_string(self) -> str:
  682. return self._raw
  683. def __add__(self, other):
  684. if PY38:
  685. result = datetime(
  686. self.year,
  687. self.month,
  688. self.day,
  689. self.hour,
  690. self.minute,
  691. self.second,
  692. self.microsecond,
  693. self.tzinfo,
  694. ).__add__(other)
  695. else:
  696. result = super().__add__(other)
  697. return self._new(result)
  698. def __sub__(self, other):
  699. if PY38:
  700. result = datetime(
  701. self.year,
  702. self.month,
  703. self.day,
  704. self.hour,
  705. self.minute,
  706. self.second,
  707. self.microsecond,
  708. self.tzinfo,
  709. ).__sub__(other)
  710. else:
  711. result = super().__sub__(other)
  712. if isinstance(result, datetime):
  713. result = self._new(result)
  714. return result
  715. def replace(self, *args: Any, **kwargs: Any) -> datetime:
  716. return self._new(super().replace(*args, **kwargs))
  717. def astimezone(self, tz: tzinfo) -> datetime:
  718. result = super().astimezone(tz)
  719. if PY38:
  720. return result
  721. return self._new(result)
  722. def _new(self, result) -> DateTime:
  723. raw = result.isoformat()
  724. return DateTime(
  725. result.year,
  726. result.month,
  727. result.day,
  728. result.hour,
  729. result.minute,
  730. result.second,
  731. result.microsecond,
  732. result.tzinfo,
  733. self._trivia,
  734. raw,
  735. )
  736. def _getstate(self, protocol=3):
  737. return (
  738. self.year,
  739. self.month,
  740. self.day,
  741. self.hour,
  742. self.minute,
  743. self.second,
  744. self.microsecond,
  745. self.tzinfo,
  746. self._trivia,
  747. self._raw,
  748. )
  749. class Date(Item, date):
  750. """
  751. A date literal.
  752. """
  753. def __new__(cls, year: int, month: int, day: int, *_: Any) -> date:
  754. return date.__new__(cls, year, month, day)
  755. def __init__(
  756. self, year: int, month: int, day: int, trivia: Trivia, raw: str
  757. ) -> None:
  758. super().__init__(trivia)
  759. self._raw = raw
  760. def unwrap(self) -> date:
  761. (year, month, day, _, _) = self._getstate()
  762. return date(year, month, day)
  763. @property
  764. def discriminant(self) -> int:
  765. return 6
  766. @property
  767. def value(self) -> date:
  768. return self
  769. def as_string(self) -> str:
  770. return self._raw
  771. def __add__(self, other):
  772. if PY38:
  773. result = date(self.year, self.month, self.day).__add__(other)
  774. else:
  775. result = super().__add__(other)
  776. return self._new(result)
  777. def __sub__(self, other):
  778. if PY38:
  779. result = date(self.year, self.month, self.day).__sub__(other)
  780. else:
  781. result = super().__sub__(other)
  782. if isinstance(result, date):
  783. result = self._new(result)
  784. return result
  785. def replace(self, *args: Any, **kwargs: Any) -> date:
  786. return self._new(super().replace(*args, **kwargs))
  787. def _new(self, result):
  788. raw = result.isoformat()
  789. return Date(result.year, result.month, result.day, self._trivia, raw)
  790. def _getstate(self, protocol=3):
  791. return (self.year, self.month, self.day, self._trivia, self._raw)
  792. class Time(Item, time):
  793. """
  794. A time literal.
  795. """
  796. def __new__(
  797. cls,
  798. hour: int,
  799. minute: int,
  800. second: int,
  801. microsecond: int,
  802. tzinfo: tzinfo | None,
  803. *_: Any,
  804. ) -> time:
  805. return time.__new__(cls, hour, minute, second, microsecond, tzinfo)
  806. def __init__(
  807. self,
  808. hour: int,
  809. minute: int,
  810. second: int,
  811. microsecond: int,
  812. tzinfo: tzinfo | None,
  813. trivia: Trivia,
  814. raw: str,
  815. ) -> None:
  816. super().__init__(trivia)
  817. self._raw = raw
  818. def unwrap(self) -> time:
  819. (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
  820. return time(hour, minute, second, microsecond, tzinfo)
  821. @property
  822. def discriminant(self) -> int:
  823. return 7
  824. @property
  825. def value(self) -> time:
  826. return self
  827. def as_string(self) -> str:
  828. return self._raw
  829. def replace(self, *args: Any, **kwargs: Any) -> time:
  830. return self._new(super().replace(*args, **kwargs))
  831. def _new(self, result):
  832. raw = result.isoformat()
  833. return Time(
  834. result.hour,
  835. result.minute,
  836. result.second,
  837. result.microsecond,
  838. result.tzinfo,
  839. self._trivia,
  840. raw,
  841. )
  842. def _getstate(self, protocol: int = 3) -> tuple:
  843. return (
  844. self.hour,
  845. self.minute,
  846. self.second,
  847. self.microsecond,
  848. self.tzinfo,
  849. self._trivia,
  850. self._raw,
  851. )
  852. class _ArrayItemGroup:
  853. __slots__ = ("value", "indent", "comma", "comment")
  854. def __init__(
  855. self,
  856. value: Item | None = None,
  857. indent: Whitespace | None = None,
  858. comma: Whitespace | None = None,
  859. comment: Comment | None = None,
  860. ) -> None:
  861. self.value = value
  862. self.indent = indent
  863. self.comma = comma
  864. self.comment = comment
  865. def __iter__(self) -> Iterator[Item]:
  866. return filter(
  867. lambda x: x is not None, (self.indent, self.value, self.comma, self.comment)
  868. )
  869. def __repr__(self) -> str:
  870. return repr(tuple(self))
  871. def is_whitespace(self) -> bool:
  872. return self.value is None and self.comment is None
  873. def __bool__(self) -> bool:
  874. try:
  875. next(iter(self))
  876. except StopIteration:
  877. return False
  878. return True
  879. class Array(Item, _CustomList):
  880. """
  881. An array literal
  882. """
  883. def __init__(
  884. self, value: list[Item], trivia: Trivia, multiline: bool = False
  885. ) -> None:
  886. super().__init__(trivia)
  887. list.__init__(
  888. self,
  889. [v for v in value if not isinstance(v, (Whitespace, Comment, Null))],
  890. )
  891. self._index_map: dict[int, int] = {}
  892. self._value = self._group_values(value)
  893. self._multiline = multiline
  894. self._reindex()
  895. def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
  896. """Group the values into (indent, value, comma, comment) tuples"""
  897. groups = []
  898. this_group = _ArrayItemGroup()
  899. for item in value:
  900. if isinstance(item, Whitespace):
  901. if "," not in item.s:
  902. groups.append(this_group)
  903. this_group = _ArrayItemGroup(indent=item)
  904. else:
  905. if this_group.value is None:
  906. # when comma is met and no value is provided, add a dummy Null
  907. this_group.value = Null()
  908. this_group.comma = item
  909. elif isinstance(item, Comment):
  910. if this_group.value is None:
  911. this_group.value = Null()
  912. this_group.comment = item
  913. elif this_group.value is None:
  914. this_group.value = item
  915. else:
  916. groups.append(this_group)
  917. this_group = _ArrayItemGroup(value=item)
  918. groups.append(this_group)
  919. return [group for group in groups if group]
  920. def unwrap(self) -> list[Any]:
  921. unwrapped = []
  922. for v in self:
  923. if hasattr(v, "unwrap"):
  924. unwrapped.append(v.unwrap())
  925. else:
  926. unwrapped.append(v)
  927. return unwrapped
  928. @property
  929. def discriminant(self) -> int:
  930. return 8
  931. @property
  932. def value(self) -> list:
  933. return self
  934. def _iter_items(self) -> Iterator[Item]:
  935. for v in self._value:
  936. yield from v
  937. def multiline(self, multiline: bool) -> Array:
  938. """Change the array to display in multiline or not.
  939. :Example:
  940. >>> a = item([1, 2, 3])
  941. >>> print(a.as_string())
  942. [1, 2, 3]
  943. >>> print(a.multiline(True).as_string())
  944. [
  945. 1,
  946. 2,
  947. 3,
  948. ]
  949. """
  950. self._multiline = multiline
  951. return self
  952. def as_string(self) -> str:
  953. if not self._multiline or not self._value:
  954. return f'[{"".join(v.as_string() for v in self._iter_items())}]'
  955. s = "[\n"
  956. s += "".join(
  957. self.trivia.indent
  958. + " " * 4
  959. + v.value.as_string()
  960. + ("," if not isinstance(v.value, Null) else "")
  961. + (v.comment.as_string() if v.comment is not None else "")
  962. + "\n"
  963. for v in self._value
  964. if v.value is not None
  965. )
  966. s += self.trivia.indent + "]"
  967. return s
  968. def _reindex(self) -> None:
  969. self._index_map.clear()
  970. index = 0
  971. for i, v in enumerate(self._value):
  972. if v.value is None or isinstance(v.value, Null):
  973. continue
  974. self._index_map[index] = i
  975. index += 1
  976. def add_line(
  977. self,
  978. *items: Any,
  979. indent: str = " ",
  980. comment: str | None = None,
  981. add_comma: bool = True,
  982. newline: bool = True,
  983. ) -> None:
  984. """Add multiple items in a line to control the format precisely.
  985. When add_comma is True, only accept actual values and
  986. ", " will be added between values automatically.
  987. :Example:
  988. >>> a = array()
  989. >>> a.add_line(1, 2, 3)
  990. >>> a.add_line(4, 5, 6)
  991. >>> a.add_line(indent="")
  992. >>> print(a.as_string())
  993. [
  994. 1, 2, 3,
  995. 4, 5, 6,
  996. ]
  997. """
  998. new_values: list[Item] = []
  999. first_indent = f"\n{indent}" if newline else indent
  1000. if first_indent:
  1001. new_values.append(Whitespace(first_indent))
  1002. whitespace = ""
  1003. data_values = []
  1004. for i, el in enumerate(items):
  1005. it = item(el, _parent=self)
  1006. if isinstance(it, Comment) or add_comma and isinstance(el, Whitespace):
  1007. raise ValueError(f"item type {type(it)} is not allowed in add_line")
  1008. if not isinstance(it, Whitespace):
  1009. if whitespace:
  1010. new_values.append(Whitespace(whitespace))
  1011. whitespace = ""
  1012. new_values.append(it)
  1013. data_values.append(it.value)
  1014. if add_comma:
  1015. new_values.append(Whitespace(","))
  1016. if i != len(items) - 1:
  1017. new_values.append(Whitespace(" "))
  1018. elif "," not in it.s:
  1019. whitespace += it.s
  1020. else:
  1021. new_values.append(it)
  1022. if whitespace:
  1023. new_values.append(Whitespace(whitespace))
  1024. if comment:
  1025. indent = " " if items else ""
  1026. new_values.append(
  1027. Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
  1028. )
  1029. list.extend(self, data_values)
  1030. if len(self._value) > 0:
  1031. last_item = self._value[-1]
  1032. last_value_item = next(
  1033. (
  1034. v
  1035. for v in self._value[::-1]
  1036. if v.value is not None and not isinstance(v.value, Null)
  1037. ),
  1038. None,
  1039. )
  1040. if last_value_item is not None:
  1041. last_value_item.comma = Whitespace(",")
  1042. if last_item.is_whitespace():
  1043. self._value[-1:-1] = self._group_values(new_values)
  1044. else:
  1045. self._value.extend(self._group_values(new_values))
  1046. else:
  1047. self._value.extend(self._group_values(new_values))
  1048. self._reindex()
  1049. def clear(self) -> None:
  1050. """Clear the array."""
  1051. list.clear(self)
  1052. self._index_map.clear()
  1053. self._value.clear()
  1054. def __len__(self) -> int:
  1055. return list.__len__(self)
  1056. def __getitem__(self, key: int | slice) -> Any:
  1057. rv = cast(Item, list.__getitem__(self, key))
  1058. if rv.is_boolean():
  1059. return bool(rv)
  1060. return rv
  1061. def __setitem__(self, key: int | slice, value: Any) -> Any:
  1062. it = item(value, _parent=self)
  1063. list.__setitem__(self, key, it)
  1064. if isinstance(key, slice):
  1065. raise ValueError("slice assignment is not supported")
  1066. if key < 0:
  1067. key += len(self)
  1068. self._value[self._index_map[key]].value = it
  1069. def insert(self, pos: int, value: Any) -> None:
  1070. it = item(value, _parent=self)
  1071. length = len(self)
  1072. if not isinstance(it, (Comment, Whitespace)):
  1073. list.insert(self, pos, it)
  1074. if pos < 0:
  1075. pos += length
  1076. if pos < 0:
  1077. pos = 0
  1078. idx = 0 # insert position of the self._value list
  1079. default_indent = " "
  1080. if pos < length:
  1081. try:
  1082. idx = self._index_map[pos]
  1083. except KeyError as e:
  1084. raise IndexError("list index out of range") from e
  1085. else:
  1086. idx = len(self._value)
  1087. if idx >= 1 and self._value[idx - 1].is_whitespace():
  1088. # The last item is a pure whitespace(\n ), insert before it
  1089. idx -= 1
  1090. if (
  1091. self._value[idx].indent is not None
  1092. and "\n" in self._value[idx].indent.s
  1093. ):
  1094. default_indent = "\n "
  1095. indent: Item | None = None
  1096. comma: Item | None = Whitespace(",") if pos < length else None
  1097. if idx < len(self._value) and not self._value[idx].is_whitespace():
  1098. # Prefer to copy the indentation from the item after
  1099. indent = self._value[idx].indent
  1100. if idx > 0:
  1101. last_item = self._value[idx - 1]
  1102. if indent is None:
  1103. indent = last_item.indent
  1104. if not isinstance(last_item.value, Null) and "\n" in default_indent:
  1105. # Copy the comma from the last item if 1) it contains a value and
  1106. # 2) the array is multiline
  1107. comma = last_item.comma
  1108. if last_item.comma is None and not isinstance(last_item.value, Null):
  1109. # Add comma to the last item to separate it from the following items.
  1110. last_item.comma = Whitespace(",")
  1111. if indent is None and (idx > 0 or "\n" in default_indent):
  1112. # apply default indent if it isn't the first item or the array is multiline.
  1113. indent = Whitespace(default_indent)
  1114. new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
  1115. self._value.insert(idx, new_item)
  1116. self._reindex()
  1117. def __delitem__(self, key: int | slice):
  1118. length = len(self)
  1119. list.__delitem__(self, key)
  1120. if isinstance(key, slice):
  1121. indices_to_remove = list(
  1122. range(key.start or 0, key.stop or length, key.step or 1)
  1123. )
  1124. else:
  1125. indices_to_remove = [length + key if key < 0 else key]
  1126. for i in sorted(indices_to_remove, reverse=True):
  1127. try:
  1128. idx = self._index_map[i]
  1129. except KeyError as e:
  1130. if not isinstance(key, slice):
  1131. raise IndexError("list index out of range") from e
  1132. else:
  1133. del self._value[idx]
  1134. if (
  1135. idx == 0
  1136. and len(self._value) > 0
  1137. and "\n" not in self._value[idx].indent.s
  1138. ):
  1139. # Remove the indentation of the first item if not newline
  1140. self._value[idx].indent = None
  1141. if len(self._value) > 0:
  1142. v = self._value[-1]
  1143. if not v.is_whitespace():
  1144. # remove the comma of the last item
  1145. v.comma = None
  1146. self._reindex()
  1147. def _getstate(self, protocol=3):
  1148. return list(self._iter_items()), self._trivia, self._multiline
  1149. class AbstractTable(Item, _CustomDict):
  1150. """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
  1151. def __init__(self, value: container.Container, trivia: Trivia):
  1152. Item.__init__(self, trivia)
  1153. self._value = value
  1154. for k, v in self._value.body:
  1155. if k is not None:
  1156. dict.__setitem__(self, k.key, v)
  1157. def unwrap(self) -> dict[str, Any]:
  1158. unwrapped = {}
  1159. for k, v in self.items():
  1160. if isinstance(k, Key):
  1161. k = k.key
  1162. if hasattr(v, "unwrap"):
  1163. v = v.unwrap()
  1164. unwrapped[k] = v
  1165. return unwrapped
  1166. @property
  1167. def value(self) -> container.Container:
  1168. return self._value
  1169. @overload
  1170. def append(self: AT, key: None, value: Comment | Whitespace) -> AT:
  1171. ...
  1172. @overload
  1173. def append(self: AT, key: Key | str, value: Any) -> AT:
  1174. ...
  1175. def append(self, key, value):
  1176. raise NotImplementedError
  1177. @overload
  1178. def add(self: AT, key: Comment | Whitespace) -> AT:
  1179. ...
  1180. @overload
  1181. def add(self: AT, key: Key | str, value: Any = ...) -> AT:
  1182. ...
  1183. def add(self, key, value=None):
  1184. if value is None:
  1185. if not isinstance(key, (Comment, Whitespace)):
  1186. msg = "Non comment/whitespace items must have an associated key"
  1187. raise ValueError(msg)
  1188. key, value = None, key
  1189. return self.append(key, value)
  1190. def remove(self: AT, key: Key | str) -> AT:
  1191. self._value.remove(key)
  1192. if isinstance(key, Key):
  1193. key = key.key
  1194. if key is not None:
  1195. dict.__delitem__(self, key)
  1196. return self
  1197. def setdefault(self, key: Key | str, default: Any) -> Any:
  1198. super().setdefault(key, default)
  1199. return self[key]
  1200. def __str__(self):
  1201. return str(self.value)
  1202. def copy(self: AT) -> AT:
  1203. return copy.copy(self)
  1204. def __repr__(self) -> str:
  1205. return repr(self.value)
  1206. def __iter__(self) -> Iterator[str]:
  1207. return iter(self._value)
  1208. def __len__(self) -> int:
  1209. return len(self._value)
  1210. def __delitem__(self, key: Key | str) -> None:
  1211. self.remove(key)
  1212. def __getitem__(self, key: Key | str) -> Item:
  1213. return cast(Item, self._value[key])
  1214. def __setitem__(self, key: Key | str, value: Any) -> None:
  1215. if not isinstance(value, Item):
  1216. value = item(value, _parent=self)
  1217. is_replace = key in self
  1218. self._value[key] = value
  1219. if key is not None:
  1220. dict.__setitem__(self, key, value)
  1221. if is_replace:
  1222. return
  1223. m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
  1224. if not m:
  1225. return
  1226. indent = m.group(1)
  1227. if not isinstance(value, Whitespace):
  1228. m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
  1229. if not m:
  1230. value.trivia.indent = indent
  1231. else:
  1232. value.trivia.indent = m.group(1) + indent + m.group(2)
  1233. class Table(AbstractTable):
  1234. """
  1235. A table literal.
  1236. """
  1237. def __init__(
  1238. self,
  1239. value: container.Container,
  1240. trivia: Trivia,
  1241. is_aot_element: bool,
  1242. is_super_table: bool | None = None,
  1243. name: str | None = None,
  1244. display_name: str | None = None,
  1245. ) -> None:
  1246. super().__init__(value, trivia)
  1247. self.name = name
  1248. self.display_name = display_name
  1249. self._is_aot_element = is_aot_element
  1250. self._is_super_table = is_super_table
  1251. @property
  1252. def discriminant(self) -> int:
  1253. return 9
  1254. def __copy__(self) -> Table:
  1255. return type(self)(
  1256. self._value.copy(),
  1257. self._trivia.copy(),
  1258. self._is_aot_element,
  1259. self._is_super_table,
  1260. self.name,
  1261. self.display_name,
  1262. )
  1263. def append(self, key: Key | str | None, _item: Any) -> Table:
  1264. """
  1265. Appends a (key, item) to the table.
  1266. """
  1267. if not isinstance(_item, Item):
  1268. _item = item(_item, _parent=self)
  1269. self._value.append(key, _item)
  1270. if isinstance(key, Key):
  1271. key = next(iter(key)).key
  1272. _item = self._value[key]
  1273. if key is not None:
  1274. dict.__setitem__(self, key, _item)
  1275. m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
  1276. if not m:
  1277. return self
  1278. indent = m.group(1)
  1279. if not isinstance(_item, Whitespace):
  1280. m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
  1281. if not m:
  1282. _item.trivia.indent = indent
  1283. else:
  1284. _item.trivia.indent = m.group(1) + indent + m.group(2)
  1285. return self
  1286. def raw_append(self, key: Key | str | None, _item: Any) -> Table:
  1287. """Similar to :meth:`append` but does not copy indentation."""
  1288. if not isinstance(_item, Item):
  1289. _item = item(_item)
  1290. self._value.append(key, _item)
  1291. if isinstance(key, Key):
  1292. key = next(iter(key)).key
  1293. _item = self._value[key]
  1294. if key is not None:
  1295. dict.__setitem__(self, key, _item)
  1296. return self
  1297. def is_aot_element(self) -> bool:
  1298. """True if the table is the direct child of an AOT element."""
  1299. return self._is_aot_element
  1300. def is_super_table(self) -> bool:
  1301. """A super table is the intermediate parent of a nested table as in [a.b.c].
  1302. If true, it won't appear in the TOML representation."""
  1303. if self._is_super_table is not None:
  1304. return self._is_super_table
  1305. # If the table has only one child and that child is a table, then it is a super table.
  1306. if len(self) != 1:
  1307. return False
  1308. only_child = next(iter(self.values()))
  1309. return isinstance(only_child, (Table, AoT))
  1310. def as_string(self) -> str:
  1311. return self._value.as_string()
  1312. # Helpers
  1313. def indent(self, indent: int) -> Table:
  1314. """Indent the table with given number of spaces."""
  1315. super().indent(indent)
  1316. m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
  1317. if not m:
  1318. indent_str = ""
  1319. else:
  1320. indent_str = m.group(1)
  1321. for _, item in self._value.body:
  1322. if not isinstance(item, Whitespace):
  1323. item.trivia.indent = indent_str + item.trivia.indent
  1324. return self
  1325. def invalidate_display_name(self):
  1326. self.display_name = None
  1327. for child in self.values():
  1328. if hasattr(child, "invalidate_display_name"):
  1329. child.invalidate_display_name()
  1330. def _getstate(self, protocol: int = 3) -> tuple:
  1331. return (
  1332. self._value,
  1333. self._trivia,
  1334. self._is_aot_element,
  1335. self._is_super_table,
  1336. self.name,
  1337. self.display_name,
  1338. )
  1339. class InlineTable(AbstractTable):
  1340. """
  1341. An inline table literal.
  1342. """
  1343. def __init__(
  1344. self, value: container.Container, trivia: Trivia, new: bool = False
  1345. ) -> None:
  1346. super().__init__(value, trivia)
  1347. self._new = new
  1348. @property
  1349. def discriminant(self) -> int:
  1350. return 10
  1351. def append(self, key: Key | str | None, _item: Any) -> InlineTable:
  1352. """
  1353. Appends a (key, item) to the table.
  1354. """
  1355. if not isinstance(_item, Item):
  1356. _item = item(_item, _parent=self)
  1357. if not isinstance(_item, (Whitespace, Comment)):
  1358. if not _item.trivia.indent and len(self._value) > 0 and not self._new:
  1359. _item.trivia.indent = " "
  1360. if _item.trivia.comment:
  1361. _item.trivia.comment = ""
  1362. self._value.append(key, _item)
  1363. if isinstance(key, Key):
  1364. key = key.key
  1365. if key is not None:
  1366. dict.__setitem__(self, key, _item)
  1367. return self
  1368. def as_string(self) -> str:
  1369. buf = "{"
  1370. last_item_idx = next(
  1371. (
  1372. i
  1373. for i in range(len(self._value.body) - 1, -1, -1)
  1374. if self._value.body[i][0] is not None
  1375. ),
  1376. None,
  1377. )
  1378. for i, (k, v) in enumerate(self._value.body):
  1379. if k is None:
  1380. if i == len(self._value.body) - 1:
  1381. if self._new:
  1382. buf = buf.rstrip(", ")
  1383. else:
  1384. buf = buf.rstrip(",")
  1385. buf += v.as_string()
  1386. continue
  1387. v_trivia_trail = v.trivia.trail.replace("\n", "")
  1388. buf += (
  1389. f"{v.trivia.indent}"
  1390. f'{k.as_string() + ("." if k.is_dotted() else "")}'
  1391. f"{k.sep}"
  1392. f"{v.as_string()}"
  1393. f"{v.trivia.comment}"
  1394. f"{v_trivia_trail}"
  1395. )
  1396. if last_item_idx is not None and i < last_item_idx:
  1397. buf += ","
  1398. if self._new:
  1399. buf += " "
  1400. buf += "}"
  1401. return buf
  1402. def __setitem__(self, key: Key | str, value: Any) -> None:
  1403. if hasattr(value, "trivia") and value.trivia.comment:
  1404. value.trivia.comment = ""
  1405. super().__setitem__(key, value)
  1406. def __copy__(self) -> InlineTable:
  1407. return type(self)(self._value.copy(), self._trivia.copy(), self._new)
  1408. def _getstate(self, protocol: int = 3) -> tuple:
  1409. return (self._value, self._trivia)
  1410. class String(str, Item):
  1411. """
  1412. A string literal.
  1413. """
  1414. def __new__(cls, t, value, original, trivia):
  1415. return super().__new__(cls, value)
  1416. def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
  1417. super().__init__(trivia)
  1418. self._t = t
  1419. self._original = original
  1420. def unwrap(self) -> str:
  1421. return str(self)
  1422. @property
  1423. def discriminant(self) -> int:
  1424. return 11
  1425. @property
  1426. def value(self) -> str:
  1427. return self
  1428. def as_string(self) -> str:
  1429. return f"{self._t.value}{decode(self._original)}{self._t.value}"
  1430. def __add__(self: ItemT, other: str) -> ItemT:
  1431. if not isinstance(other, str):
  1432. return NotImplemented
  1433. result = super().__add__(other)
  1434. original = self._original + getattr(other, "_original", other)
  1435. return self._new(result, original)
  1436. def _new(self, result: str, original: str) -> String:
  1437. return String(self._t, result, original, self._trivia)
  1438. def _getstate(self, protocol=3):
  1439. return self._t, str(self), self._original, self._trivia
  1440. @classmethod
  1441. def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String:
  1442. value = decode(value)
  1443. invalid = type_.invalid_sequences
  1444. if any(c in value for c in invalid):
  1445. raise InvalidStringError(value, invalid, type_.value)
  1446. escaped = type_.escaped_sequences
  1447. string_value = escape_string(value, escaped) if escape and escaped else value
  1448. return cls(type_, decode(value), string_value, Trivia())
  1449. class AoT(Item, _CustomList):
  1450. """
  1451. An array of table literal
  1452. """
  1453. def __init__(
  1454. self, body: list[Table], name: str | None = None, parsed: bool = False
  1455. ) -> None:
  1456. self.name = name
  1457. self._body: list[Table] = []
  1458. self._parsed = parsed
  1459. super().__init__(Trivia(trail=""))
  1460. for table in body:
  1461. self.append(table)
  1462. def unwrap(self) -> list[dict[str, Any]]:
  1463. unwrapped = []
  1464. for t in self._body:
  1465. if hasattr(t, "unwrap"):
  1466. unwrapped.append(t.unwrap())
  1467. else:
  1468. unwrapped.append(t)
  1469. return unwrapped
  1470. @property
  1471. def body(self) -> list[Table]:
  1472. return self._body
  1473. @property
  1474. def discriminant(self) -> int:
  1475. return 12
  1476. @property
  1477. def value(self) -> list[dict[Any, Any]]:
  1478. return [v.value for v in self._body]
  1479. def __len__(self) -> int:
  1480. return len(self._body)
  1481. @overload
  1482. def __getitem__(self, key: slice) -> list[Table]:
  1483. ...
  1484. @overload
  1485. def __getitem__(self, key: int) -> Table:
  1486. ...
  1487. def __getitem__(self, key):
  1488. return self._body[key]
  1489. def __setitem__(self, key: slice | int, value: Any) -> None:
  1490. raise NotImplementedError
  1491. def __delitem__(self, key: slice | int) -> None:
  1492. del self._body[key]
  1493. list.__delitem__(self, key)
  1494. def insert(self, index: int, value: dict) -> None:
  1495. value = item(value, _parent=self)
  1496. if not isinstance(value, Table):
  1497. raise ValueError(f"Unsupported insert value type: {type(value)}")
  1498. length = len(self)
  1499. if index < 0:
  1500. index += length
  1501. if index < 0:
  1502. index = 0
  1503. elif index >= length:
  1504. index = length
  1505. m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
  1506. if m:
  1507. indent = m.group(1)
  1508. m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
  1509. if not m:
  1510. value.trivia.indent = indent
  1511. else:
  1512. value.trivia.indent = m.group(1) + indent + m.group(2)
  1513. prev_table = self._body[index - 1] if 0 < index and length else None
  1514. next_table = self._body[index + 1] if index < length - 1 else None
  1515. if not self._parsed:
  1516. if prev_table and "\n" not in value.trivia.indent:
  1517. value.trivia.indent = "\n" + value.trivia.indent
  1518. if next_table and "\n" not in next_table.trivia.indent:
  1519. next_table.trivia.indent = "\n" + next_table.trivia.indent
  1520. self._body.insert(index, value)
  1521. list.insert(self, index, value)
  1522. def invalidate_display_name(self):
  1523. """Call ``invalidate_display_name`` on the contained tables"""
  1524. for child in self:
  1525. if hasattr(child, "invalidate_display_name"):
  1526. child.invalidate_display_name()
  1527. def as_string(self) -> str:
  1528. b = ""
  1529. for table in self._body:
  1530. b += table.as_string()
  1531. return b
  1532. def __repr__(self) -> str:
  1533. return f"<AoT {self.value}>"
  1534. def _getstate(self, protocol=3):
  1535. return self._body, self.name, self._parsed
  1536. class Null(Item):
  1537. """
  1538. A null item.
  1539. """
  1540. def __init__(self) -> None:
  1541. pass
  1542. def unwrap(self) -> None:
  1543. return None
  1544. @property
  1545. def discriminant(self) -> int:
  1546. return -1
  1547. @property
  1548. def value(self) -> None:
  1549. return None
  1550. def as_string(self) -> str:
  1551. return ""
  1552. def _getstate(self, protocol=3) -> tuple:
  1553. return ()