items.py 56 KB

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