| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026 |
- from __future__ import annotations
- import abc
- import copy
- import dataclasses
- import inspect
- import re
- import string
- from collections.abc import Collection
- from collections.abc import Iterable
- from collections.abc import Iterator
- from collections.abc import Sequence
- from datetime import date
- from datetime import datetime
- from datetime import time
- from datetime import tzinfo
- from enum import Enum
- from typing import TYPE_CHECKING
- from typing import Any
- from typing import TypeVar
- from typing import cast
- from typing import overload
- from tomlkit._compat import PY38
- from tomlkit._compat import decode
- from tomlkit._types import _CustomDict
- from tomlkit._types import _CustomFloat
- from tomlkit._types import _CustomInt
- from tomlkit._types import _CustomList
- from tomlkit._types import wrap_method
- from tomlkit._utils import CONTROL_CHARS
- from tomlkit._utils import escape_string
- from tomlkit.exceptions import ConvertError
- from tomlkit.exceptions import InvalidStringError
- if TYPE_CHECKING:
- from typing import Protocol
- from tomlkit import container
- class Encoder(Protocol):
- def __call__(
- self, __value: Any, _parent: Item | None = None, _sort_keys: bool = False
- ) -> Item: ...
- ItemT = TypeVar("ItemT", bound="Item")
- CUSTOM_ENCODERS: list[Encoder] = []
- AT = TypeVar("AT", bound="AbstractTable")
- @overload
- def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ...
- @overload
- def item(value: int, _parent: Item | None = ..., _sort_keys: bool = ...) -> Integer: ...
- @overload
- def item(value: float, _parent: Item | None = ..., _sort_keys: bool = ...) -> Float: ...
- @overload
- def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> String: ...
- @overload
- def item(
- value: datetime, _parent: Item | None = ..., _sort_keys: bool = ...
- ) -> DateTime: ...
- @overload
- def item(value: date, _parent: Item | None = ..., _sort_keys: bool = ...) -> Date: ...
- @overload
- def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Time: ...
- @overload
- def item(
- value: Sequence[dict], _parent: Item | None = ..., _sort_keys: bool = ...
- ) -> AoT: ...
- @overload
- def item(
- value: Sequence, _parent: Item | None = ..., _sort_keys: bool = ...
- ) -> Array: ...
- @overload
- def item(value: dict, _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ...
- @overload
- def item(value: dict, _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ...
- @overload
- def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ...
- def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item:
- """Create a TOML item from a Python object.
- :Example:
- >>> item(42)
- 42
- >>> item([1, 2, 3])
- [1, 2, 3]
- >>> item({'a': 1, 'b': 2})
- a = 1
- b = 2
- """
- from tomlkit.container import Container
- if isinstance(value, Item):
- return value
- if isinstance(value, bool):
- return Bool(value, Trivia())
- elif isinstance(value, int):
- return Integer(value, Trivia(), str(value))
- elif isinstance(value, float):
- return Float(value, Trivia(), str(value))
- elif isinstance(value, dict):
- table_constructor = (
- InlineTable if isinstance(_parent, (Array, InlineTable)) else Table
- )
- val = table_constructor(Container(), Trivia(), False)
- for k, v in sorted(
- value.items(),
- key=lambda i: (isinstance(i[1], dict), i[0]) if _sort_keys else 1,
- ):
- val[k] = item(v, _parent=val, _sort_keys=_sort_keys)
- return val
- elif isinstance(value, (list, tuple)):
- if (
- value
- and all(isinstance(v, dict) for v in value)
- and (_parent is None or isinstance(_parent, Table))
- ):
- a = AoT([])
- table_constructor = Table
- else:
- a = Array([], Trivia())
- table_constructor = InlineTable
- for v in value:
- if isinstance(v, dict):
- table = table_constructor(Container(), Trivia(), True)
- for k, _v in sorted(
- v.items(),
- key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1),
- ):
- i = item(_v, _parent=table, _sort_keys=_sort_keys)
- if isinstance(table, InlineTable):
- i.trivia.trail = ""
- table[k] = i
- v = table
- a.append(v)
- return a
- elif isinstance(value, str):
- return String.from_raw(value)
- elif isinstance(value, datetime):
- return DateTime(
- value.year,
- value.month,
- value.day,
- value.hour,
- value.minute,
- value.second,
- value.microsecond,
- value.tzinfo,
- Trivia(),
- value.isoformat().replace("+00:00", "Z"),
- )
- elif isinstance(value, date):
- return Date(value.year, value.month, value.day, Trivia(), value.isoformat())
- elif isinstance(value, time):
- return Time(
- value.hour,
- value.minute,
- value.second,
- value.microsecond,
- value.tzinfo,
- Trivia(),
- value.isoformat(),
- )
- else:
- for encoder in CUSTOM_ENCODERS:
- try:
- # Check if encoder accepts keyword arguments for backward compatibility
- sig = inspect.signature(encoder)
- if "_parent" in sig.parameters or any(
- p.kind == p.VAR_KEYWORD for p in sig.parameters.values()
- ):
- # New style encoder that can accept additional parameters
- rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys)
- else:
- # Old style encoder that only accepts value
- rv = encoder(value)
- except ConvertError:
- pass
- else:
- if not isinstance(rv, Item):
- raise ConvertError(
- f"Custom encoder is expected to return an instance of Item, got {type(rv)}"
- )
- return rv
- raise ConvertError(f"Unable to convert an object of {type(value)} to a TOML item")
- class StringType(Enum):
- # Single Line Basic
- SLB = '"'
- # Multi Line Basic
- MLB = '"""'
- # Single Line Literal
- SLL = "'"
- # Multi Line Literal
- MLL = "'''"
- @classmethod
- def select(cls, literal=False, multiline=False) -> StringType:
- return {
- (False, False): cls.SLB,
- (False, True): cls.MLB,
- (True, False): cls.SLL,
- (True, True): cls.MLL,
- }[(literal, multiline)]
- @property
- def escaped_sequences(self) -> Collection[str]:
- # https://toml.io/en/v1.0.0#string
- escaped_in_basic = CONTROL_CHARS | {"\\"}
- allowed_in_multiline = {"\n", "\r"}
- return {
- StringType.SLB: escaped_in_basic | {'"'},
- StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline,
- StringType.SLL: (),
- StringType.MLL: (),
- }[self]
- @property
- def invalid_sequences(self) -> Collection[str]:
- # https://toml.io/en/v1.0.0#string
- forbidden_in_literal = CONTROL_CHARS - {"\t"}
- allowed_in_multiline = {"\n", "\r"}
- return {
- StringType.SLB: (),
- StringType.MLB: (),
- StringType.SLL: forbidden_in_literal | {"'"},
- StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline,
- }[self]
- @property
- def unit(self) -> str:
- return self.value[0]
- def is_basic(self) -> bool:
- return self in {StringType.SLB, StringType.MLB}
- def is_literal(self) -> bool:
- return self in {StringType.SLL, StringType.MLL}
- def is_singleline(self) -> bool:
- return self in {StringType.SLB, StringType.SLL}
- def is_multiline(self) -> bool:
- return self in {StringType.MLB, StringType.MLL}
- def toggle(self) -> StringType:
- return {
- StringType.SLB: StringType.MLB,
- StringType.MLB: StringType.SLB,
- StringType.SLL: StringType.MLL,
- StringType.MLL: StringType.SLL,
- }[self]
- class BoolType(Enum):
- TRUE = "true"
- FALSE = "false"
- def __bool__(self):
- return {BoolType.TRUE: True, BoolType.FALSE: False}[self]
- def __iter__(self):
- return iter(self.value)
- def __len__(self):
- return len(self.value)
- @dataclasses.dataclass
- class Trivia:
- """
- Trivia information (aka metadata).
- """
- # Whitespace before a value.
- indent: str = ""
- # Whitespace after a value, but before a comment.
- comment_ws: str = ""
- # Comment, starting with # character, or empty string if no comment.
- comment: str = ""
- # Trailing newline.
- trail: str = "\n"
- def copy(self) -> Trivia:
- return dataclasses.replace(self)
- class KeyType(Enum):
- """
- The type of a Key.
- Keys can be bare (unquoted), or quoted using basic ("), or literal (')
- quotes following the same escaping rules as single-line StringType.
- """
- Bare = ""
- Basic = '"'
- Literal = "'"
- class Key(abc.ABC):
- """Base class for a key"""
- sep: str
- _original: str
- _keys: list[SingleKey]
- _dotted: bool
- key: str
- @abc.abstractmethod
- def __hash__(self) -> int:
- pass
- @abc.abstractmethod
- def __eq__(self, __o: object) -> bool:
- pass
- def is_dotted(self) -> bool:
- """If the key is followed by other keys"""
- return self._dotted
- def __iter__(self) -> Iterator[SingleKey]:
- return iter(self._keys)
- def concat(self, other: Key) -> DottedKey:
- """Concatenate keys into a dotted key"""
- keys = self._keys + other._keys
- return DottedKey(keys, sep=self.sep)
- def is_multi(self) -> bool:
- """Check if the key contains multiple keys"""
- return len(self._keys) > 1
- def as_string(self) -> str:
- """The TOML representation"""
- return self._original
- def __str__(self) -> str:
- return self.as_string()
- def __repr__(self) -> str:
- return f"<Key {self.as_string()}>"
- class SingleKey(Key):
- """A single key"""
- def __init__(
- self,
- k: str,
- t: KeyType | None = None,
- sep: str | None = None,
- original: str | None = None,
- ) -> None:
- if not isinstance(k, str):
- raise TypeError("Keys must be strings")
- if t is None:
- if not k or any(
- c not in string.ascii_letters + string.digits + "-" + "_" for c in k
- ):
- t = KeyType.Basic
- else:
- t = KeyType.Bare
- self.t = t
- if sep is None:
- sep = " = "
- self.sep = sep
- self.key = k
- if original is None:
- key_str = escape_string(k) if t == KeyType.Basic else k
- original = f"{t.value}{key_str}{t.value}"
- self._original = original
- self._keys = [self]
- self._dotted = False
- @property
- def delimiter(self) -> str:
- """The delimiter: double quote/single quote/none"""
- return self.t.value
- def is_bare(self) -> bool:
- """Check if the key is bare"""
- return self.t == KeyType.Bare
- def __hash__(self) -> int:
- return hash(self.key)
- def __eq__(self, other: Any) -> bool:
- if isinstance(other, Key):
- return isinstance(other, SingleKey) and self.key == other.key
- return self.key == other
- class DottedKey(Key):
- def __init__(
- self,
- keys: Iterable[SingleKey],
- sep: str | None = None,
- original: str | None = None,
- ) -> None:
- self._keys = list(keys)
- if original is None:
- original = ".".join(k.as_string() for k in self._keys)
- self.sep = " = " if sep is None else sep
- self._original = original
- self._dotted = False
- self.key = ".".join(k.key for k in self._keys)
- def __hash__(self) -> int:
- return hash(tuple(self._keys))
- def __eq__(self, __o: object) -> bool:
- return isinstance(__o, DottedKey) and self._keys == __o._keys
- class Item:
- """
- An item within a TOML document.
- """
- def __init__(self, trivia: Trivia) -> None:
- self._trivia = trivia
- @property
- def trivia(self) -> Trivia:
- """The trivia element associated with this item"""
- return self._trivia
- @property
- def discriminant(self) -> int:
- raise NotImplementedError()
- def as_string(self) -> str:
- """The TOML representation"""
- raise NotImplementedError()
- @property
- def value(self) -> Any:
- return self
- def unwrap(self) -> Any:
- """Returns as pure python object (ppo)"""
- raise NotImplementedError()
- # Helpers
- def comment(self, comment: str) -> Item:
- """Attach a comment to this item"""
- if not comment.strip().startswith("#"):
- comment = "# " + comment
- self._trivia.comment_ws = " "
- self._trivia.comment = comment
- return self
- def indent(self, indent: int) -> Item:
- """Indent this item with given number of spaces"""
- if self._trivia.indent.startswith("\n"):
- self._trivia.indent = "\n" + " " * indent
- else:
- self._trivia.indent = " " * indent
- return self
- def is_boolean(self) -> bool:
- return isinstance(self, Bool)
- def is_table(self) -> bool:
- return isinstance(self, Table)
- def is_inline_table(self) -> bool:
- return isinstance(self, InlineTable)
- def is_aot(self) -> bool:
- return isinstance(self, AoT)
- def _getstate(self, protocol=3):
- return (self._trivia,)
- def __reduce__(self):
- return self.__reduce_ex__(2)
- def __reduce_ex__(self, protocol):
- return self.__class__, self._getstate(protocol)
- class Whitespace(Item):
- """
- A whitespace literal.
- """
- def __init__(self, s: str, fixed: bool = False) -> None:
- self._s = s
- self._fixed = fixed
- @property
- def s(self) -> str:
- return self._s
- @property
- def value(self) -> str:
- """The wrapped string of the whitespace"""
- return self._s
- @property
- def trivia(self) -> Trivia:
- raise RuntimeError("Called trivia on a Whitespace variant.")
- @property
- def discriminant(self) -> int:
- return 0
- def is_fixed(self) -> bool:
- """If the whitespace is fixed, it can't be merged or discarded from the output."""
- return self._fixed
- def as_string(self) -> str:
- return self._s
- def __repr__(self) -> str:
- return f"<{self.__class__.__name__} {self._s!r}>"
- def _getstate(self, protocol=3):
- return self._s, self._fixed
- class Comment(Item):
- """
- A comment literal.
- """
- @property
- def discriminant(self) -> int:
- return 1
- def as_string(self) -> str:
- return (
- f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}"
- )
- def __str__(self) -> str:
- return f"{self._trivia.indent}{decode(self._trivia.comment)}"
- class Integer(Item, _CustomInt):
- """
- An integer literal.
- """
- def __new__(cls, value: int, trivia: Trivia, raw: str) -> Integer:
- return int.__new__(cls, value)
- def __init__(self, value: int, trivia: Trivia, raw: str) -> None:
- super().__init__(trivia)
- self._original = value
- self._raw = raw
- self._sign = False
- if re.match(r"^[+\-]\d+$", raw):
- self._sign = True
- def unwrap(self) -> int:
- return self._original
- __int__ = unwrap
- def __hash__(self) -> int:
- return hash(self.unwrap())
- @property
- def discriminant(self) -> int:
- return 2
- @property
- def value(self) -> int:
- """The wrapped integer value"""
- return self
- def as_string(self) -> str:
- return self._raw
- def _new(self, result):
- raw = str(result)
- if self._sign and result >= 0:
- raw = f"+{raw}"
- return Integer(result, self._trivia, raw)
- def _getstate(self, protocol=3):
- return int(self), self._trivia, self._raw
- # int methods
- __abs__ = wrap_method(int.__abs__)
- __add__ = wrap_method(int.__add__)
- __and__ = wrap_method(int.__and__)
- __ceil__ = wrap_method(int.__ceil__)
- __eq__ = int.__eq__
- __floor__ = wrap_method(int.__floor__)
- __floordiv__ = wrap_method(int.__floordiv__)
- __invert__ = wrap_method(int.__invert__)
- __le__ = int.__le__
- __lshift__ = wrap_method(int.__lshift__)
- __lt__ = int.__lt__
- __mod__ = wrap_method(int.__mod__)
- __mul__ = wrap_method(int.__mul__)
- __neg__ = wrap_method(int.__neg__)
- __or__ = wrap_method(int.__or__)
- __pos__ = wrap_method(int.__pos__)
- __pow__ = wrap_method(int.__pow__)
- __radd__ = wrap_method(int.__radd__)
- __rand__ = wrap_method(int.__rand__)
- __rfloordiv__ = wrap_method(int.__rfloordiv__)
- __rlshift__ = wrap_method(int.__rlshift__)
- __rmod__ = wrap_method(int.__rmod__)
- __rmul__ = wrap_method(int.__rmul__)
- __ror__ = wrap_method(int.__ror__)
- __round__ = wrap_method(int.__round__)
- __rpow__ = wrap_method(int.__rpow__)
- __rrshift__ = wrap_method(int.__rrshift__)
- __rshift__ = wrap_method(int.__rshift__)
- __rxor__ = wrap_method(int.__rxor__)
- __trunc__ = wrap_method(int.__trunc__)
- __xor__ = wrap_method(int.__xor__)
- def __rtruediv__(self, other):
- result = int.__rtruediv__(self, other)
- if result is NotImplemented:
- return result
- return Float._new(self, result)
- def __truediv__(self, other):
- result = int.__truediv__(self, other)
- if result is NotImplemented:
- return result
- return Float._new(self, result)
- class Float(Item, _CustomFloat):
- """
- A float literal.
- """
- def __new__(cls, value: float, trivia: Trivia, raw: str) -> Float:
- return float.__new__(cls, value)
- def __init__(self, value: float, trivia: Trivia, raw: str) -> None:
- super().__init__(trivia)
- self._original = value
- self._raw = raw
- self._sign = False
- if re.match(r"^[+\-].+$", raw):
- self._sign = True
- def unwrap(self) -> float:
- return self._original
- __float__ = unwrap
- def __hash__(self) -> int:
- return hash(self.unwrap())
- @property
- def discriminant(self) -> int:
- return 3
- @property
- def value(self) -> float:
- """The wrapped float value"""
- return self
- def as_string(self) -> str:
- return self._raw
- def _new(self, result):
- raw = str(result)
- if self._sign and result >= 0:
- raw = f"+{raw}"
- return Float(result, self._trivia, raw)
- def _getstate(self, protocol=3):
- return float(self), self._trivia, self._raw
- # float methods
- __abs__ = wrap_method(float.__abs__)
- __add__ = wrap_method(float.__add__)
- __eq__ = float.__eq__
- __floordiv__ = wrap_method(float.__floordiv__)
- __le__ = float.__le__
- __lt__ = float.__lt__
- __mod__ = wrap_method(float.__mod__)
- __mul__ = wrap_method(float.__mul__)
- __neg__ = wrap_method(float.__neg__)
- __pos__ = wrap_method(float.__pos__)
- __pow__ = wrap_method(float.__pow__)
- __radd__ = wrap_method(float.__radd__)
- __rfloordiv__ = wrap_method(float.__rfloordiv__)
- __rmod__ = wrap_method(float.__rmod__)
- __rmul__ = wrap_method(float.__rmul__)
- __round__ = wrap_method(float.__round__)
- __rpow__ = wrap_method(float.__rpow__)
- __rtruediv__ = wrap_method(float.__rtruediv__)
- __truediv__ = wrap_method(float.__truediv__)
- __trunc__ = float.__trunc__
- __ceil__ = float.__ceil__
- __floor__ = float.__floor__
- class Bool(Item):
- """
- A boolean literal.
- """
- def __init__(self, t: int, trivia: Trivia) -> None:
- super().__init__(trivia)
- self._value = bool(t)
- def unwrap(self) -> bool:
- return bool(self)
- @property
- def discriminant(self) -> int:
- return 4
- @property
- def value(self) -> bool:
- """The wrapped boolean value"""
- return self._value
- def as_string(self) -> str:
- return str(self._value).lower()
- def _getstate(self, protocol=3):
- return self._value, self._trivia
- def __bool__(self):
- return self._value
- __nonzero__ = __bool__
- def __eq__(self, other):
- if not isinstance(other, bool):
- return NotImplemented
- return other == self._value
- def __hash__(self):
- return hash(self._value)
- def __repr__(self):
- return repr(self._value)
- class DateTime(Item, datetime):
- """
- A datetime literal.
- """
- def __new__(
- cls,
- year: int,
- month: int,
- day: int,
- hour: int,
- minute: int,
- second: int,
- microsecond: int,
- tzinfo: tzinfo | None,
- *_: Any,
- **kwargs: Any,
- ) -> datetime:
- return datetime.__new__(
- cls,
- year,
- month,
- day,
- hour,
- minute,
- second,
- microsecond,
- tzinfo=tzinfo,
- **kwargs,
- )
- def __init__(
- self,
- year: int,
- month: int,
- day: int,
- hour: int,
- minute: int,
- second: int,
- microsecond: int,
- tzinfo: tzinfo | None,
- trivia: Trivia | None = None,
- raw: str | None = None,
- **kwargs: Any,
- ) -> None:
- super().__init__(trivia or Trivia())
- self._raw = raw or self.isoformat()
- def unwrap(self) -> datetime:
- (
- year,
- month,
- day,
- hour,
- minute,
- second,
- microsecond,
- tzinfo,
- _,
- _,
- ) = self._getstate()
- return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
- @property
- def discriminant(self) -> int:
- return 5
- @property
- def value(self) -> datetime:
- return self
- def as_string(self) -> str:
- return self._raw
- def __add__(self, other):
- if PY38:
- result = datetime(
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- self.tzinfo,
- ).__add__(other)
- else:
- result = super().__add__(other)
- return self._new(result)
- def __sub__(self, other):
- if PY38:
- result = datetime(
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- self.tzinfo,
- ).__sub__(other)
- else:
- result = super().__sub__(other)
- if isinstance(result, datetime):
- result = self._new(result)
- return result
- def replace(self, *args: Any, **kwargs: Any) -> datetime:
- return self._new(super().replace(*args, **kwargs))
- def astimezone(self, tz: tzinfo) -> datetime:
- result = super().astimezone(tz)
- if PY38:
- return result
- return self._new(result)
- def _new(self, result) -> DateTime:
- raw = result.isoformat()
- return DateTime(
- result.year,
- result.month,
- result.day,
- result.hour,
- result.minute,
- result.second,
- result.microsecond,
- result.tzinfo,
- self._trivia,
- raw,
- )
- def _getstate(self, protocol=3):
- return (
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- self.tzinfo,
- self._trivia,
- self._raw,
- )
- class Date(Item, date):
- """
- A date literal.
- """
- def __new__(cls, year: int, month: int, day: int, *_: Any) -> date:
- return date.__new__(cls, year, month, day)
- def __init__(
- self,
- year: int,
- month: int,
- day: int,
- trivia: Trivia | None = None,
- raw: str = "",
- ) -> None:
- super().__init__(trivia or Trivia())
- self._raw = raw
- def unwrap(self) -> date:
- (year, month, day, _, _) = self._getstate()
- return date(year, month, day)
- @property
- def discriminant(self) -> int:
- return 6
- @property
- def value(self) -> date:
- return self
- def as_string(self) -> str:
- return self._raw
- def __add__(self, other):
- if PY38:
- result = date(self.year, self.month, self.day).__add__(other)
- else:
- result = super().__add__(other)
- return self._new(result)
- def __sub__(self, other):
- if PY38:
- result = date(self.year, self.month, self.day).__sub__(other)
- else:
- result = super().__sub__(other)
- if isinstance(result, date):
- result = self._new(result)
- return result
- def replace(self, *args: Any, **kwargs: Any) -> date:
- return self._new(super().replace(*args, **kwargs))
- def _new(self, result):
- raw = result.isoformat()
- return Date(result.year, result.month, result.day, self._trivia, raw)
- def _getstate(self, protocol=3):
- return (self.year, self.month, self.day, self._trivia, self._raw)
- class Time(Item, time):
- """
- A time literal.
- """
- def __new__(
- cls,
- hour: int,
- minute: int,
- second: int,
- microsecond: int,
- tzinfo: tzinfo | None,
- *_: Any,
- ) -> time:
- return time.__new__(cls, hour, minute, second, microsecond, tzinfo)
- def __init__(
- self,
- hour: int,
- minute: int,
- second: int,
- microsecond: int,
- tzinfo: tzinfo | None,
- trivia: Trivia | None = None,
- raw: str = "",
- ) -> None:
- super().__init__(trivia or Trivia())
- self._raw = raw
- def unwrap(self) -> time:
- (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
- return time(hour, minute, second, microsecond, tzinfo)
- @property
- def discriminant(self) -> int:
- return 7
- @property
- def value(self) -> time:
- return self
- def as_string(self) -> str:
- return self._raw
- def replace(self, *args: Any, **kwargs: Any) -> time:
- return self._new(super().replace(*args, **kwargs))
- def _new(self, result):
- raw = result.isoformat()
- return Time(
- result.hour,
- result.minute,
- result.second,
- result.microsecond,
- result.tzinfo,
- self._trivia,
- raw,
- )
- def _getstate(self, protocol: int = 3) -> tuple:
- return (
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- self.tzinfo,
- self._trivia,
- self._raw,
- )
- class _ArrayItemGroup:
- __slots__ = ("comma", "comment", "indent", "value")
- def __init__(
- self,
- value: Item | None = None,
- indent: Whitespace | None = None,
- comma: Whitespace | None = None,
- comment: Comment | None = None,
- ) -> None:
- self.value = value
- self.indent = indent
- self.comma = comma
- self.comment = comment
- def __iter__(self) -> Iterator[Item]:
- return filter(
- lambda x: x is not None, (self.indent, self.value, self.comma, self.comment)
- )
- def __repr__(self) -> str:
- return repr(tuple(self))
- def is_whitespace(self) -> bool:
- return self.value is None and self.comment is None
- def __bool__(self) -> bool:
- try:
- next(iter(self))
- except StopIteration:
- return False
- return True
- class Array(Item, _CustomList):
- """
- An array literal
- """
- def __init__(
- self, value: list[Item], trivia: Trivia, multiline: bool = False
- ) -> None:
- super().__init__(trivia)
- list.__init__(
- self,
- [v for v in value if not isinstance(v, (Whitespace, Comment, Null))],
- )
- self._index_map: dict[int, int] = {}
- self._value = self._group_values(value)
- self._multiline = multiline
- self._reindex()
- def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
- """Group the values into (indent, value, comma, comment) tuples"""
- groups = []
- this_group = _ArrayItemGroup()
- start_new_group = False
- for item in value:
- if isinstance(item, Whitespace):
- if "," not in item.s or start_new_group:
- groups.append(this_group)
- this_group = _ArrayItemGroup(indent=item)
- start_new_group = False
- else:
- if this_group.value is None:
- # when comma is met and no value is provided, add a dummy Null
- this_group.value = Null()
- this_group.comma = item
- elif isinstance(item, Comment):
- if this_group.value is None:
- this_group.value = Null()
- this_group.comment = item
- # Comments are the last item in a group.
- start_new_group = True
- elif this_group.value is None:
- this_group.value = item
- else:
- groups.append(this_group)
- this_group = _ArrayItemGroup(value=item)
- groups.append(this_group)
- return [group for group in groups if group]
- def unwrap(self) -> list[Any]:
- unwrapped = []
- for v in self:
- if hasattr(v, "unwrap"):
- unwrapped.append(v.unwrap())
- else:
- unwrapped.append(v)
- return unwrapped
- @property
- def discriminant(self) -> int:
- return 8
- @property
- def value(self) -> list:
- return self
- def _iter_items(self) -> Iterator[Item]:
- for v in self._value:
- yield from v
- def multiline(self, multiline: bool) -> Array:
- """Change the array to display in multiline or not.
- :Example:
- >>> a = item([1, 2, 3])
- >>> print(a.as_string())
- [1, 2, 3]
- >>> print(a.multiline(True).as_string())
- [
- 1,
- 2,
- 3,
- ]
- """
- self._multiline = multiline
- return self
- def as_string(self) -> str:
- if not self._multiline or not self._value:
- return f"[{''.join(v.as_string() for v in self._iter_items())}]"
- s = "[\n"
- s += "".join(
- self.trivia.indent
- + " " * 4
- + v.value.as_string()
- + ("," if not isinstance(v.value, Null) else "")
- + (v.comment.as_string() if v.comment is not None else "")
- + "\n"
- for v in self._value
- if v.value is not None
- )
- s += self.trivia.indent + "]"
- return s
- def _reindex(self) -> None:
- self._index_map.clear()
- index = 0
- for i, v in enumerate(self._value):
- if v.value is None or isinstance(v.value, Null):
- continue
- self._index_map[index] = i
- index += 1
- def add_line(
- self,
- *items: Any,
- indent: str = " ",
- comment: str | None = None,
- add_comma: bool = True,
- newline: bool = True,
- ) -> None:
- """Add multiple items in a line to control the format precisely.
- When add_comma is True, only accept actual values and
- ", " will be added between values automatically.
- :Example:
- >>> a = array()
- >>> a.add_line(1, 2, 3)
- >>> a.add_line(4, 5, 6)
- >>> a.add_line(indent="")
- >>> print(a.as_string())
- [
- 1, 2, 3,
- 4, 5, 6,
- ]
- """
- new_values: list[Item] = []
- first_indent = f"\n{indent}" if newline else indent
- if first_indent:
- new_values.append(Whitespace(first_indent))
- whitespace = ""
- data_values = []
- for i, el in enumerate(items):
- it = item(el, _parent=self)
- if isinstance(it, Comment) or (add_comma and isinstance(el, Whitespace)):
- raise ValueError(f"item type {type(it)} is not allowed in add_line")
- if not isinstance(it, Whitespace):
- if whitespace:
- new_values.append(Whitespace(whitespace))
- whitespace = ""
- new_values.append(it)
- data_values.append(it.value)
- if add_comma:
- new_values.append(Whitespace(","))
- if i != len(items) - 1:
- new_values.append(Whitespace(" "))
- elif "," not in it.s:
- whitespace += it.s
- else:
- new_values.append(it)
- if whitespace:
- new_values.append(Whitespace(whitespace))
- if comment:
- indent = " " if items else ""
- new_values.append(
- Comment(Trivia(indent=indent, comment=f"# {comment}", trail=""))
- )
- list.extend(self, data_values)
- if len(self._value) > 0:
- last_item = self._value[-1]
- last_value_item = next(
- (
- v
- for v in self._value[::-1]
- if v.value is not None and not isinstance(v.value, Null)
- ),
- None,
- )
- if last_value_item is not None:
- last_value_item.comma = Whitespace(",")
- if last_item.is_whitespace():
- self._value[-1:-1] = self._group_values(new_values)
- else:
- self._value.extend(self._group_values(new_values))
- else:
- self._value.extend(self._group_values(new_values))
- self._reindex()
- def clear(self) -> None:
- """Clear the array."""
- list.clear(self)
- self._index_map.clear()
- self._value.clear()
- def __len__(self) -> int:
- return list.__len__(self)
- def item(self, index: int) -> Item:
- rv = list.__getitem__(self, index)
- return cast(Item, rv)
- def __getitem__(self, key: int | slice) -> Any:
- rv = list.__getitem__(self, key)
- if isinstance(rv, Bool):
- return rv.value
- return rv
- def __setitem__(self, key: int | slice, value: Any) -> Any:
- it = item(value, _parent=self)
- list.__setitem__(self, key, it)
- if isinstance(key, slice):
- raise ValueError("slice assignment is not supported")
- if key < 0:
- key += len(self)
- self._value[self._index_map[key]].value = it
- def insert(self, pos: int, value: Any) -> None:
- it = item(value, _parent=self)
- length = len(self)
- if not isinstance(it, (Comment, Whitespace)):
- list.insert(self, pos, it)
- if pos < 0:
- pos += length
- if pos < 0:
- pos = 0
- idx = 0 # insert position of the self._value list
- default_indent = " "
- if pos < length:
- try:
- idx = self._index_map[pos]
- except KeyError as e:
- raise IndexError("list index out of range") from e
- else:
- idx = len(self._value)
- if idx >= 1 and self._value[idx - 1].is_whitespace():
- # The last item is a pure whitespace(\n ), insert before it
- idx -= 1
- if (
- self._value[idx].indent is not None
- and "\n" in self._value[idx].indent.s
- ):
- default_indent = "\n "
- indent: Item | None = None
- comma: Item | None = Whitespace(",") if pos < length else None
- if idx < len(self._value) and not self._value[idx].is_whitespace():
- # Prefer to copy the indentation from the item after
- indent = self._value[idx].indent
- if idx > 0:
- last_item = self._value[idx - 1]
- if indent is None:
- indent = last_item.indent
- if not isinstance(last_item.value, Null) and "\n" in default_indent:
- # Copy the comma from the last item if 1) it contains a value and
- # 2) the array is multiline
- comma = last_item.comma
- if last_item.comma is None and not isinstance(last_item.value, Null):
- # Add comma to the last item to separate it from the following items.
- last_item.comma = Whitespace(",")
- if indent is None and (idx > 0 or "\n" in default_indent):
- # apply default indent if it isn't the first item or the array is multiline.
- indent = Whitespace(default_indent)
- new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma)
- self._value.insert(idx, new_item)
- self._reindex()
- def __delitem__(self, key: int | slice):
- length = len(self)
- list.__delitem__(self, key)
- if isinstance(key, slice):
- indices_to_remove = list(
- range(key.start or 0, key.stop or length, key.step or 1)
- )
- else:
- indices_to_remove = [length + key if key < 0 else key]
- for i in sorted(indices_to_remove, reverse=True):
- try:
- idx = self._index_map[i]
- except KeyError as e:
- if not isinstance(key, slice):
- raise IndexError("list index out of range") from e
- else:
- group_rm = self._value[idx]
- del self._value[idx]
- if (
- idx == 0
- and len(self._value) > 0
- and self._value[idx].indent
- and "\n" not in self._value[idx].indent.s
- ):
- # Remove the indentation of the first item if not newline
- self._value[idx].indent = None
- comma_in_indent = (
- group_rm.indent is not None and "," in group_rm.indent.s
- )
- comma_in_comma = group_rm.comma is not None and "," in group_rm.comma.s
- if comma_in_indent and comma_in_comma:
- # Removed group had both commas. Add one to the next group.
- group = self._value[idx] if len(self._value) > idx else None
- if group is not None:
- if group.indent is None:
- group.indent = Whitespace(",")
- elif "," not in group.indent.s:
- # Insert the comma after the newline
- try:
- newline_index = group.indent.s.index("\n")
- group.indent._s = (
- group.indent.s[: newline_index + 1]
- + ","
- + group.indent.s[newline_index + 1 :]
- )
- except ValueError:
- group.indent._s = "," + group.indent.s
- elif not comma_in_indent and not comma_in_comma:
- # Removed group had no commas. Remove the next comma found.
- for j in range(idx, len(self._value)):
- group = self._value[j]
- if group.indent is not None and "," in group.indent.s:
- group.indent._s = group.indent.s.replace(",", "", 1)
- break
- if group_rm.indent is not None and "\n" in group_rm.indent.s:
- # Restore the removed group's newline onto the next group
- # if the next group does not have a newline.
- # i.e. the two were on the same line
- group = self._value[idx] if len(self._value) > idx else None
- if group is not None and (
- group.indent is None or "\n" not in group.indent.s
- ):
- group.indent = group_rm.indent
- if len(self._value) > 0:
- v = self._value[-1]
- if not v.is_whitespace():
- # remove the comma of the last item
- v.comma = None
- self._reindex()
- def _getstate(self, protocol=3):
- return list(self._iter_items()), self._trivia, self._multiline
- class AbstractTable(Item, _CustomDict):
- """Common behaviour of both :class:`Table` and :class:`InlineTable`"""
- def __init__(self, value: container.Container, trivia: Trivia):
- Item.__init__(self, trivia)
- self._value = value
- for k, v in self._value.body:
- if k is not None:
- dict.__setitem__(self, k.key, v)
- def unwrap(self) -> dict[str, Any]:
- unwrapped = {}
- for k, v in self.items():
- if isinstance(k, Key):
- k = k.key
- if hasattr(v, "unwrap"):
- v = v.unwrap()
- unwrapped[k] = v
- return unwrapped
- @property
- def value(self) -> container.Container:
- return self._value
- @overload
- def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ...
- @overload
- def append(self: AT, key: Key | str, value: Any) -> AT: ...
- def append(self, key, value):
- raise NotImplementedError
- @overload
- def add(self: AT, key: Comment | Whitespace) -> AT: ...
- @overload
- def add(self: AT, key: Key | str, value: Any = ...) -> AT: ...
- def add(self, key, value=None):
- if value is None:
- if not isinstance(key, (Comment, Whitespace)):
- msg = "Non comment/whitespace items must have an associated key"
- raise ValueError(msg)
- key, value = None, key
- return self.append(key, value)
- def remove(self: AT, key: Key | str) -> AT:
- self._value.remove(key)
- if isinstance(key, Key):
- key = key.key
- if key is not None:
- dict.__delitem__(self, key)
- return self
- def item(self, key: Key | str) -> Item:
- return self._value.item(key)
- def setdefault(self, key: Key | str, default: Any) -> Any:
- super().setdefault(key, default)
- return self[key]
- def __str__(self):
- return str(self.value)
- def copy(self: AT) -> AT:
- return copy.copy(self)
- def __repr__(self) -> str:
- return repr(self.value)
- def __iter__(self) -> Iterator[str]:
- return iter(self._value)
- def __len__(self) -> int:
- return len(self._value)
- def __delitem__(self, key: Key | str) -> None:
- self.remove(key)
- def __getitem__(self, key: Key | str) -> Item:
- return cast(Item, self._value[key])
- def __setitem__(self, key: Key | str, value: Any) -> None:
- if not isinstance(value, Item):
- value = item(value, _parent=self)
- is_replace = key in self
- self._value[key] = value
- if key is not None:
- dict.__setitem__(self, key, value)
- if is_replace:
- return
- m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
- if not m:
- return
- indent = m.group(1)
- if not isinstance(value, Whitespace):
- m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
- if not m:
- value.trivia.indent = indent
- else:
- value.trivia.indent = m.group(1) + indent + m.group(2)
- class Table(AbstractTable):
- """
- A table literal.
- """
- def __init__(
- self,
- value: container.Container,
- trivia: Trivia,
- is_aot_element: bool,
- is_super_table: bool | None = None,
- name: str | None = None,
- display_name: str | None = None,
- ) -> None:
- super().__init__(value, trivia)
- self.name = name
- self.display_name = display_name
- self._is_aot_element = is_aot_element
- self._is_super_table = is_super_table
- @property
- def discriminant(self) -> int:
- return 9
- def __copy__(self) -> Table:
- return type(self)(
- self._value.copy(),
- self._trivia.copy(),
- self._is_aot_element,
- self._is_super_table,
- self.name,
- self.display_name,
- )
- def append(self, key: Key | str | None, _item: Any) -> Table:
- """
- Appends a (key, item) to the table.
- """
- if not isinstance(_item, Item):
- _item = item(_item, _parent=self)
- self._value.append(key, _item)
- if isinstance(key, Key):
- key = next(iter(key)).key
- _item = self._value[key]
- if key is not None:
- dict.__setitem__(self, key, _item)
- m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
- if not m:
- return self
- indent = m.group(1)
- if not isinstance(_item, Whitespace):
- m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent)
- if not m:
- _item.trivia.indent = indent
- else:
- _item.trivia.indent = m.group(1) + indent + m.group(2)
- return self
- def raw_append(self, key: Key | str | None, _item: Any) -> Table:
- """Similar to :meth:`append` but does not copy indentation."""
- if not isinstance(_item, Item):
- _item = item(_item)
- self._value.append(key, _item, validate=False)
- if isinstance(key, Key):
- key = next(iter(key)).key
- _item = self._value[key]
- if key is not None:
- dict.__setitem__(self, key, _item)
- return self
- def is_aot_element(self) -> bool:
- """True if the table is the direct child of an AOT element."""
- return self._is_aot_element
- def is_super_table(self) -> bool:
- """A super table is the intermediate parent of a nested table as in [a.b.c].
- If true, it won't appear in the TOML representation."""
- if self._is_super_table is not None:
- return self._is_super_table
- if not self:
- return False
- # If the table has children and all children are tables, then it is a super table.
- for k, child in self.items():
- if not isinstance(k, Key):
- k = SingleKey(k)
- index = self.value._map[k]
- if isinstance(index, tuple):
- return False
- real_key = self.value.body[index][0]
- if (
- not isinstance(child, (Table, AoT))
- or real_key is None
- or real_key.is_dotted()
- ):
- return False
- return True
- def as_string(self) -> str:
- return self._value.as_string()
- # Helpers
- def indent(self, indent: int) -> Table:
- """Indent the table with given number of spaces."""
- super().indent(indent)
- m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
- if not m:
- indent_str = ""
- else:
- indent_str = m.group(1)
- for _, item in self._value.body:
- if not isinstance(item, Whitespace):
- item.trivia.indent = indent_str + item.trivia.indent
- return self
- def invalidate_display_name(self):
- """Call ``invalidate_display_name`` on the contained tables"""
- self.display_name = None
- for child in self.values():
- if hasattr(child, "invalidate_display_name"):
- child.invalidate_display_name()
- def _getstate(self, protocol: int = 3) -> tuple:
- return (
- self._value,
- self._trivia,
- self._is_aot_element,
- self._is_super_table,
- self.name,
- self.display_name,
- )
- class InlineTable(AbstractTable):
- """
- An inline table literal.
- """
- def __init__(
- self, value: container.Container, trivia: Trivia, new: bool = False
- ) -> None:
- super().__init__(value, trivia)
- self._new = new
- @property
- def discriminant(self) -> int:
- return 10
- def append(self, key: Key | str | None, _item: Any) -> InlineTable:
- """
- Appends a (key, item) to the table.
- """
- if not isinstance(_item, Item):
- _item = item(_item, _parent=self)
- if not isinstance(_item, (Whitespace, Comment)):
- if not _item.trivia.indent and len(self._value) > 0 and not self._new:
- _item.trivia.indent = " "
- if _item.trivia.comment:
- _item.trivia.comment = ""
- self._value.append(key, _item)
- if isinstance(key, Key):
- key = key.key
- if key is not None:
- dict.__setitem__(self, key, _item)
- return self
- def as_string(self) -> str:
- buf = "{"
- last_item_idx = next(
- (
- i
- for i in range(len(self._value.body) - 1, -1, -1)
- if self._value.body[i][0] is not None
- ),
- None,
- )
- for i, (k, v) in enumerate(self._value.body):
- if k is None:
- if i == len(self._value.body) - 1:
- if self._new:
- buf = buf.rstrip(", ")
- else:
- buf = buf.rstrip(",")
- buf += v.as_string()
- continue
- v_trivia_trail = v.trivia.trail.replace("\n", "")
- buf += (
- f"{v.trivia.indent}"
- f"{k.as_string() + ('.' if k.is_dotted() else '')}"
- f"{k.sep}"
- f"{v.as_string()}"
- f"{v.trivia.comment}"
- f"{v_trivia_trail}"
- )
- if last_item_idx is not None and i < last_item_idx:
- buf += ","
- if self._new:
- buf += " "
- buf += "}"
- return buf
- def __setitem__(self, key: Key | str, value: Any) -> None:
- if hasattr(value, "trivia") and value.trivia.comment:
- value.trivia.comment = ""
- super().__setitem__(key, value)
- def __copy__(self) -> InlineTable:
- return type(self)(self._value.copy(), self._trivia.copy(), self._new)
- def _getstate(self, protocol: int = 3) -> tuple:
- return (self._value, self._trivia)
- class String(str, Item):
- """
- A string literal.
- """
- def __new__(cls, t, value, original, trivia):
- return super().__new__(cls, value)
- def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None:
- super().__init__(trivia)
- self._t = t
- self._original = original
- def unwrap(self) -> str:
- return str(self)
- @property
- def discriminant(self) -> int:
- return 11
- @property
- def value(self) -> str:
- return self
- def as_string(self) -> str:
- return f"{self._t.value}{decode(self._original)}{self._t.value}"
- @property
- def type(self) -> StringType:
- return self._t
- def __add__(self: ItemT, other: str) -> ItemT:
- if not isinstance(other, str):
- return NotImplemented
- result = super().__add__(other)
- original = self._original + getattr(other, "_original", other)
- return self._new(result, original)
- def _new(self, result: str, original: str) -> String:
- return String(self._t, result, original, self._trivia)
- def _getstate(self, protocol=3):
- return self._t, str(self), self._original, self._trivia
- @classmethod
- def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String:
- value = decode(value)
- invalid = type_.invalid_sequences
- if any(c in value for c in invalid):
- raise InvalidStringError(value, invalid, type_.value)
- escaped = type_.escaped_sequences
- string_value = escape_string(value, escaped) if escape and escaped else value
- return cls(type_, decode(value), string_value, Trivia())
- class AoT(Item, _CustomList):
- """
- An array of table literal
- """
- def __init__(
- self, body: list[Table], name: str | None = None, parsed: bool = False
- ) -> None:
- self.name = name
- self._body: list[Table] = []
- self._parsed = parsed
- super().__init__(Trivia(trail=""))
- for table in body:
- self.append(table)
- def unwrap(self) -> list[dict[str, Any]]:
- unwrapped = []
- for t in self._body:
- if hasattr(t, "unwrap"):
- unwrapped.append(t.unwrap())
- else:
- unwrapped.append(t)
- return unwrapped
- @property
- def body(self) -> list[Table]:
- return self._body
- @property
- def discriminant(self) -> int:
- return 12
- @property
- def value(self) -> list[dict[Any, Any]]:
- return [v.value for v in self._body]
- def __len__(self) -> int:
- return len(self._body)
- @overload
- def __getitem__(self, key: slice) -> list[Table]: ...
- @overload
- def __getitem__(self, key: int) -> Table: ...
- def __getitem__(self, key):
- return self._body[key]
- def __setitem__(self, key: slice | int, value: Any) -> None:
- self._body[key] = item(value, _parent=self)
- def __delitem__(self, key: slice | int) -> None:
- del self._body[key]
- list.__delitem__(self, key)
- def insert(self, index: int, value: dict) -> None:
- value = item(value, _parent=self)
- if not isinstance(value, Table):
- raise ValueError(f"Unsupported insert value type: {type(value)}")
- length = len(self)
- if index < 0:
- index += length
- if index < 0:
- index = 0
- elif index >= length:
- index = length
- m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent)
- if m:
- indent = m.group(1)
- m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent)
- if not m:
- value.trivia.indent = indent
- else:
- value.trivia.indent = m.group(1) + indent + m.group(2)
- prev_table = self._body[index - 1] if 0 < index and length else None
- next_table = self._body[index + 1] if index < length - 1 else None
- if not self._parsed:
- if prev_table and "\n" not in value.trivia.indent:
- value.trivia.indent = "\n" + value.trivia.indent
- if next_table and "\n" not in next_table.trivia.indent:
- next_table.trivia.indent = "\n" + next_table.trivia.indent
- self._body.insert(index, value)
- list.insert(self, index, value)
- def invalidate_display_name(self):
- """Call ``invalidate_display_name`` on the contained tables"""
- for child in self:
- if hasattr(child, "invalidate_display_name"):
- child.invalidate_display_name()
- def as_string(self) -> str:
- b = ""
- for table in self._body:
- b += table.as_string()
- return b
- def __repr__(self) -> str:
- return f"<AoT {self.value}>"
- def _getstate(self, protocol=3):
- return self._body, self.name, self._parsed
- class Null(Item):
- """
- A null item.
- """
- def __init__(self) -> None:
- super().__init__(Trivia(trail=""))
- def unwrap(self) -> None:
- return None
- @property
- def discriminant(self) -> int:
- return -1
- @property
- def value(self) -> None:
- return None
- def as_string(self) -> str:
- return ""
- def _getstate(self, protocol=3) -> tuple:
- return ()
|