Update tomlkit==0.9.2

Used:

 python -m invoke vendoring.update --package=tomlkit
This commit is contained in:
Oz N Tiram
2022-07-25 17:18:43 +02:00
parent 1abbab63e0
commit 8faa74cdc9
11 changed files with 1423 additions and 1260 deletions
+30 -1
View File
@@ -1,3 +1,4 @@
from .api import TOMLDocument
from .api import aot
from .api import array
from .api import boolean
@@ -5,6 +6,7 @@ from .api import comment
from .api import date
from .api import datetime
from .api import document
from .api import dump
from .api import dumps
from .api import float_
from .api import inline_table
@@ -12,6 +14,7 @@ from .api import integer
from .api import item
from .api import key
from .api import key_value
from .api import load
from .api import loads
from .api import nl
from .api import parse
@@ -22,4 +25,30 @@ from .api import value
from .api import ws
__version__ = "0.7.2"
__version__ = "0.9.2"
__all__ = [
"aot",
"array",
"boolean",
"comment",
"date",
"datetime",
"document",
"dump",
"dumps",
"float_",
"inline_table",
"integer",
"item",
"key",
"key_value",
"load",
"loads",
"nl",
"parse",
"string",
"table",
"time",
"value",
"ws",
]
+5 -161
View File
@@ -1,171 +1,15 @@
import re
import sys
try:
from datetime import timezone
except ImportError:
from datetime import datetime
from datetime import timedelta
from datetime import tzinfo
class timezone(tzinfo):
__slots__ = "_offset", "_name"
# Sentinel value to disallow None
_Omitted = object()
def __new__(cls, offset, name=_Omitted):
if not isinstance(offset, timedelta):
raise TypeError("offset must be a timedelta")
if name is cls._Omitted:
if not offset:
return cls.utc
name = None
elif not isinstance(name, str):
raise TypeError("name must be a string")
if not cls._minoffset <= offset <= cls._maxoffset:
raise ValueError(
"offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
"timedelta(hours=24)."
)
return cls._create(offset, name)
@classmethod
def _create(cls, offset, name=None):
self = tzinfo.__new__(cls)
self._offset = offset
self._name = name
return self
def __getinitargs__(self):
"""pickle support"""
if self._name is None:
return (self._offset,)
return (self._offset, self._name)
def __eq__(self, other):
if type(other) != timezone:
return False
return self._offset == other._offset
def __hash__(self):
return hash(self._offset)
def __repr__(self):
"""Convert to formal string, for repr().
>>> tz = timezone.utc
>>> repr(tz)
'datetime.timezone.utc'
>>> tz = timezone(timedelta(hours=-5), 'EST')
>>> repr(tz)
"datetime.timezone(datetime.timedelta(-1, 68400), 'EST')"
"""
if self is self.utc:
return "datetime.timezone.utc"
if self._name is None:
return "%s.%s(%r)" % (
self.__class__.__module__,
self.__class__.__name__,
self._offset,
)
return "%s.%s(%r, %r)" % (
self.__class__.__module__,
self.__class__.__name__,
self._offset,
self._name,
)
def __str__(self):
return self.tzname(None)
def utcoffset(self, dt):
if isinstance(dt, datetime) or dt is None:
return self._offset
raise TypeError(
"utcoffset() argument must be a datetime instance" " or None"
)
def tzname(self, dt):
if isinstance(dt, datetime) or dt is None:
if self._name is None:
return self._name_from_offset(self._offset)
return self._name
raise TypeError("tzname() argument must be a datetime instance" " or None")
def dst(self, dt):
if isinstance(dt, datetime) or dt is None:
return None
raise TypeError("dst() argument must be a datetime instance" " or None")
def fromutc(self, dt):
if isinstance(dt, datetime):
if dt.tzinfo is not self:
raise ValueError("fromutc: dt.tzinfo " "is not self")
return dt + self._offset
raise TypeError("fromutc() argument must be a datetime instance" " or None")
_maxoffset = timedelta(hours=23, minutes=59)
_minoffset = -_maxoffset
@staticmethod
def _name_from_offset(delta):
if not delta:
return "UTC"
if delta < timedelta(0):
sign = "-"
delta = -delta
else:
sign = "+"
hours, rest = divmod(delta, timedelta(hours=1))
minutes, rest = divmod(rest, timedelta(minutes=1))
seconds = rest.seconds
microseconds = rest.microseconds
if microseconds:
return ("UTC{}{:02d}:{:02d}:{:02d}.{:06d}").format(
sign, hours, minutes, seconds, microseconds
)
if seconds:
return "UTC{}{:02d}:{:02d}:{:02d}".format(sign, hours, minutes, seconds)
return "UTC{}{:02d}:{:02d}".format(sign, hours, minutes)
timezone.utc = timezone._create(timedelta(0))
timezone.min = timezone._create(timezone._minoffset)
timezone.max = timezone._create(timezone._maxoffset)
from typing import Any
from typing import List
from typing import Optional
PY2 = sys.version_info[0] == 2
PY36 = sys.version_info >= (3, 6)
PY38 = sys.version_info >= (3, 8)
if PY2:
unicode = unicode
chr = unichr
long = long
else:
unicode = str
chr = chr
long = int
if PY36:
OrderedDict = dict
else:
from collections import OrderedDict
try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
def decode(string, encodings=None):
if not PY2 and not isinstance(string, bytes):
return string
if PY2 and isinstance(string, unicode):
def decode(string: Any, encodings: Optional[List[str]] = None):
if not isinstance(string, bytes):
return string
encodings = encodings or ["utf-8", "latin1", "ascii"]
+15 -22
View File
@@ -1,28 +1,23 @@
import re
from collections.abc import Mapping
from datetime import date
from datetime import datetime
from datetime import time
from datetime import timedelta
from datetime import timezone
from typing import Union
from ._compat import decode
from ._compat import timezone
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
RFC_3339_LOOSE = re.compile(
"^"
r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date
"("
"([T ])?" # Separator
"([Tt ])?" # Separator
r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?" # Time
r"((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
")?"
"$"
)
@@ -30,9 +25,9 @@ RFC_3339_LOOSE = re.compile(
RFC_3339_DATETIME = re.compile(
"^"
"([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date
"[T ]" # Separator
"[Tt ]" # Separator
r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time
r"((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
"$"
)
@@ -45,7 +40,7 @@ RFC_3339_TIME = re.compile(
_utc = timezone(timedelta(), "UTC")
def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time]
def parse_rfc3339(string: str) -> Union[datetime, date, time]:
m = RFC_3339_DATETIME.match(string)
if m:
year = int(m.group(1))
@@ -57,12 +52,12 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time]
microsecond = 0
if m.group(7):
microsecond = int(("{:<06s}".format(m.group(8)))[:6])
microsecond = int((f"{m.group(8):<06s}")[:6])
if m.group(9):
# Timezone
tz = m.group(9)
if tz == "Z":
if tz.upper() == "Z":
tzinfo = _utc
else:
sign = m.group(11)[0]
@@ -71,9 +66,7 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time]
if sign == "-":
offset = -offset
tzinfo = timezone(
offset, "{}{}:{}".format(sign, m.group(12), m.group(13))
)
tzinfo = timezone(offset, f"{sign}{m.group(12)}:{m.group(13)}")
return datetime(
year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
@@ -97,7 +90,7 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time]
microsecond = 0
if m.group(4):
microsecond = int(("{:<06s}".format(m.group(5)))[:6])
microsecond = int((f"{m.group(5):<06s}")[:6])
return time(hour, minute, second, microsecond)
@@ -108,7 +101,7 @@ _escaped = {"b": "\b", "t": "\t", "n": "\n", "f": "\f", "r": "\r", '"': '"', "\\
_escapes = {v: k for k, v in _escaped.items()}
def escape_string(s):
def escape_string(s: str) -> str:
s = decode(s)
res = []
@@ -136,9 +129,9 @@ def escape_string(s):
return "".join(res)
def merge_dicts(d1, d2):
def merge_dicts(d1: dict, d2: dict) -> dict:
for k, v in d2.items():
if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping):
merge_dicts(d1[k], d2[k])
if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping):
merge_dicts(d1[k], v)
else:
d1[k] = d2[k]
+154 -30
View File
@@ -1,20 +1,27 @@
import datetime as _datetime
from collections.abc import Mapping
from typing import IO
from typing import Iterable
from typing import Tuple
from typing import Union
from ._utils import parse_rfc3339
from .container import Container
from .exceptions import UnexpectedCharError
from .items import AoT
from .items import Array
from .items import Bool
from .items import Comment
from .items import Date
from .items import DateTime
from .items import DottedKey
from .items import Float
from .items import InlineTable
from .items import Integer
from .items import Item as _Item
from .items import Key
from .items import SingleKey
from .items import String
from .items import Table
from .items import Time
@@ -22,10 +29,10 @@ from .items import Trivia
from .items import Whitespace
from .items import item
from .parser import Parser
from .toml_document import TOMLDocument as _TOMLDocument
from .toml_document import TOMLDocument
def loads(string): # type: (str) -> _TOMLDocument
def loads(string: Union[str, bytes]) -> TOMLDocument:
"""
Parses a string into a TOMLDocument.
@@ -34,48 +41,76 @@ def loads(string): # type: (str) -> _TOMLDocument
return parse(string)
def dumps(data, sort_keys=False): # type: (_TOMLDocument, bool) -> str
def dumps(data: Mapping, sort_keys: bool = False) -> str:
"""
Dumps a TOMLDocument into a string.
"""
if not isinstance(data, _TOMLDocument) and isinstance(data, dict):
data = item(data, _sort_keys=sort_keys)
if not isinstance(data, Container) and isinstance(data, Mapping):
data = item(dict(data), _sort_keys=sort_keys)
return data.as_string()
try:
# data should be a `Container` (and therefore implement `as_string`)
# for all type safe invocations of this function
return data.as_string() # type: ignore[attr-defined]
except AttributeError as ex:
msg = f"Expecting Mapping or TOML Container, {type(data)} given"
raise TypeError(msg) from ex
def parse(string): # type: (str) -> _TOMLDocument
def load(fp: IO) -> TOMLDocument:
"""
Parses a string into a TOMLDocument.
Load toml document from a file-like object.
"""
return parse(fp.read())
def dump(data: Mapping, fp: IO[str], *, sort_keys: bool = False) -> None:
"""
Dump a TOMLDocument into a writable file stream.
:param data: a dict-like object to dump
:param sort_keys: if true, sort the keys in alphabetic order
"""
fp.write(dumps(data, sort_keys=sort_keys))
def parse(string: Union[str, bytes]) -> TOMLDocument:
"""
Parses a string or bytes into a TOMLDocument.
"""
return Parser(string).parse()
def document(): # type: () -> _TOMLDocument
def document() -> TOMLDocument:
"""
Returns a new TOMLDocument instance.
"""
return _TOMLDocument()
return TOMLDocument()
# Items
def integer(raw): # type: (str) -> Integer
def integer(raw: Union[str, int]) -> Integer:
"""Create an integer item from a number or string."""
return item(int(raw))
def float_(raw): # type: (str) -> Float
def float_(raw: Union[str, float]) -> Float:
"""Create an float item from a number or string."""
return item(float(raw))
def boolean(raw): # type: (str) -> Bool
def boolean(raw: str) -> Bool:
"""Turn `true` or `false` into a boolean item."""
return item(raw == "true")
def string(raw): # type: (str) -> String
def string(raw: str) -> String:
"""Create a string item."""
return item(raw)
def date(raw): # type: (str) -> Date
def date(raw: str) -> Date:
"""Create a TOML date."""
value = parse_rfc3339(raw)
if not isinstance(value, _datetime.date):
raise ValueError("date() only accepts date strings.")
@@ -83,7 +118,8 @@ def date(raw): # type: (str) -> Date
return item(value)
def time(raw): # type: (str) -> Time
def time(raw: str) -> Time:
"""Create a TOML time."""
value = parse_rfc3339(raw)
if not isinstance(value, _datetime.time):
raise ValueError("time() only accepts time strings.")
@@ -91,7 +127,8 @@ def time(raw): # type: (str) -> Time
return item(value)
def datetime(raw): # type: (str) -> DateTime
def datetime(raw: str) -> DateTime:
"""Create a TOML datetime."""
value = parse_rfc3339(raw)
if not isinstance(value, _datetime.datetime):
raise ValueError("datetime() only accepts datetime strings.")
@@ -99,44 +136,131 @@ def datetime(raw): # type: (str) -> DateTime
return item(value)
def array(raw=None): # type: (str) -> Array
def array(raw: str = None) -> Array:
"""Create an array item for its string representation.
:Example:
>>> array("[1, 2, 3]") # Create from a string
[1, 2, 3]
>>> a = array()
>>> a.extend([1, 2, 3]) # Create from a list
>>> a
[1, 2, 3]
"""
if raw is None:
raw = "[]"
return value(raw)
def table(): # type: () -> Table
return Table(Container(), Trivia(), False)
def table(is_super_table: bool = False) -> Table:
"""Create an empty table.
:param is_super_table: if true, the table is a super table
:Example:
>>> doc = document()
>>> foo = table(True)
>>> bar = table()
>>> bar.update({'x': 1})
>>> foo.append('bar', bar)
>>> doc.append('foo', foo)
>>> print(doc.as_string())
[foo.bar]
x = 1
"""
return Table(Container(), Trivia(), False, is_super_table)
def inline_table(): # type: () -> InlineTable
def inline_table() -> InlineTable:
"""Create an inline table.
:Example:
>>> table = inline_table()
>>> table.update({'x': 1, 'y': 2})
>>> print(table.as_string())
{x = 1, y = 2}
"""
return InlineTable(Container(), Trivia(), new=True)
def aot(): # type: () -> AoT
def aot() -> AoT:
"""Create an array of table.
:Example:
>>> doc = document()
>>> aot = aot()
>>> aot.append(item({'x': 1}))
>>> doc.append('foo', aot)
>>> print(doc.as_string())
[[foo]]
x = 1
"""
return AoT([])
def key(k): # type: (str) -> Key
return Key(k)
def key(k: Union[str, Iterable[str]]) -> Key:
"""Create a key from a string. When a list of string is given,
it will create a dotted key.
:Example:
>>> doc = document()
>>> doc.append(key('foo'), 1)
>>> doc.append(key(['bar', 'baz']), 2)
>>> print(doc.as_string())
foo = 1
bar.baz = 2
"""
if isinstance(k, str):
return SingleKey(k)
return DottedKey([key(_k) for _k in k])
def value(raw): # type: (str) -> _Item
return Parser(raw)._parse_value()
def value(raw: str) -> _Item:
"""Parse a simple value from a string.
:Example:
>>> value("1")
1
>>> value("true")
True
>>> value("[1, 2, 3]")
[1, 2, 3]
"""
parser = Parser(raw)
v = parser._parse_value()
if not parser.end():
raise parser.parse_error(UnexpectedCharError, char=parser._current)
return v
def key_value(src): # type: (str) -> Tuple[Key, _Item]
def key_value(src: str) -> Tuple[Key, _Item]:
"""Parse a key-value pair from a string.
:Example:
>>> key_value("foo = 1")
(Key('foo'), 1)
"""
return Parser(src)._parse_key_value()
def ws(src): # type: (str) -> Whitespace
def ws(src: str) -> Whitespace:
"""Create a whitespace from a string."""
return Whitespace(src, fixed=True)
def nl(): # type: () -> Whitespace
def nl() -> Whitespace:
"""Create a newline item."""
return ws("\n")
def comment(string): # type: (str) -> Comment
def comment(string: str) -> Comment:
"""Create a comment item."""
return Comment(Trivia(comment_ws=" ", comment="# " + string))
+253 -191
View File
@@ -1,52 +1,53 @@
from __future__ import unicode_literals
import copy
from typing import Any
from typing import Dict
from typing import Generator
from typing import Iterator
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
from ._compat import MutableMapping
from ._compat import decode
from ._utils import merge_dicts
from .exceptions import KeyAlreadyPresent
from .exceptions import NonExistentKey
from .exceptions import ParseError
from .exceptions import TOMLKitError
from .items import AoT
from .items import Comment
from .items import Item
from .items import Key
from .items import Null
from .items import SingleKey
from .items import Table
from .items import Trivia
from .items import Whitespace
from .items import _CustomDict
from .items import item as _item
_NOT_SET = object()
class Container(MutableMapping, dict):
class Container(_CustomDict):
"""
A container for items within a TOMLDocument.
This class implements the `dict` interface with copy/deepcopy protocol.
"""
def __init__(self, parsed=False): # type: (bool) -> None
self._map = {} # type: Dict[Key, int]
self._body = [] # type: List[Tuple[Optional[Key], Item]]
def __init__(self, parsed: bool = False) -> None:
self._map: Dict[Key, int] = {}
self._body: List[Tuple[Optional[Key], Item]] = []
self._parsed = parsed
self._table_keys = []
@property
def body(self): # type: () -> List[Tuple[Optional[Key], Item]]
def body(self) -> List[Tuple[Optional[Key], Item]]:
return self._body
@property
def value(self): # type: () -> Dict[Any, Any]
def value(self) -> Dict[Any, Any]:
d = {}
for k, v in self._body:
if k is None:
@@ -65,10 +66,10 @@ class Container(MutableMapping, dict):
return d
def parsing(self, parsing): # type: (bool) -> None
def parsing(self, parsing: bool) -> None:
self._parsed = parsing
for k, v in self._body:
for _, v in self._body:
if isinstance(v, Table):
v.value.parsing(parsing)
elif isinstance(v, AoT):
@@ -76,10 +77,17 @@ class Container(MutableMapping, dict):
t.value.parsing(parsing)
def add(
self, key, item=None
): # type: (Union[Key, Item, str], Optional[Item]) -> Container
self, key: Union[Key, Item, str], item: Optional[Item] = None
) -> "Container":
"""
Adds an item to the current Container.
:Example:
>>> # add a key-value pair
>>> doc.add('key', 'value')
>>> # add a comment or whitespace or newline
>>> doc.add(comment('# comment'))
"""
if item is None:
if not isinstance(key, (Comment, Whitespace)):
@@ -91,26 +99,90 @@ class Container(MutableMapping, dict):
return self.append(key, item)
def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container
def _handle_dotted_key(self, key: Key, value: Item) -> None:
names = tuple(iter(key))
name = names[0]
name._dotted = True
if name in self:
if not isinstance(value, Table):
table = Table(Container(True), Trivia(), False, is_super_table=True)
_table = table
for i, _name in enumerate(names[1:]):
if i == len(names) - 2:
_name.sep = key.sep
_table.append(_name, value)
else:
_name._dotted = True
_table.append(
_name,
Table(
Container(True),
Trivia(),
False,
is_super_table=i < len(names) - 2,
),
)
_table = _table[_name]
value = table
self.append(name, value)
return
else:
table = Table(Container(True), Trivia(), False, is_super_table=True)
self.append(name, table)
for i, _name in enumerate(names[1:]):
if i == len(names) - 2:
_name.sep = key.sep
table.append(_name, value)
else:
_name._dotted = True
if _name in table.value:
table = table.value[_name]
else:
table.append(
_name,
Table(
Container(True),
Trivia(),
False,
is_super_table=i < len(names) - 2,
),
)
table = table[_name]
def append(self, key: Union[Key, str, None], item: Item) -> "Container":
"""Similar to :meth:`add` but both key and value must be given."""
if not isinstance(key, Key) and key is not None:
key = Key(key)
key = SingleKey(key)
if not isinstance(item, Item):
item = _item(item)
if key is not None and key.is_multi():
self._handle_dotted_key(key, item)
return self
if isinstance(item, (AoT, Table)) and item.name is None:
item.name = key.key
if (
isinstance(item, Table)
and self._body
and not self._parsed
and not item.trivia.indent
):
item.trivia.indent = "\n"
prev = self._previous_item()
prev_ws = isinstance(prev, Whitespace) or ends_with_withespace(prev)
if isinstance(item, Table):
if item.name != key.key:
item.invalidate_display_name()
if self._body and not (self._parsed or item.trivia.indent or prev_ws):
item.trivia.indent = "\n"
if isinstance(item, AoT) and self._body and not self._parsed:
if item and "\n" not in item[0].trivia.indent:
item.invalidate_display_name()
if item and not ("\n" in item[0].trivia.indent or prev_ws):
item[0].trivia.indent = "\n" + item[0].trivia.indent
if key is not None and key in self:
@@ -165,8 +237,15 @@ class Container(MutableMapping, dict):
return self
# Create a new element to replace the old one
current = copy.deepcopy(current)
for k, v in item.value.body:
current.append(k, v)
self._body[
current_idx[-1]
if isinstance(current_idx, tuple)
else current_idx
] = (current_body_element[0], current)
return self
elif current_body_element[0].is_dotted():
@@ -192,11 +271,9 @@ class Container(MutableMapping, dict):
# item that is not a table and insert after it
# If no such item exists, insert at the top of the table
key_after = None
idx = 0
for k, v in self._body:
for i, (k, v) in enumerate(self._body):
if isinstance(v, Null):
# This happens only after deletion
continue
continue # Null elements are inserted after deletion
if isinstance(v, Whitespace) and not v.is_fixed():
continue
@@ -204,8 +281,7 @@ class Container(MutableMapping, dict):
if not is_table and isinstance(v, (Table, AoT)):
break
key_after = k or idx
idx += 1
key_after = k or i # last scalar, Array or InlineTable value
if key_after is not None:
if isinstance(key_after, int):
@@ -213,10 +289,11 @@ class Container(MutableMapping, dict):
return self._insert_at(key_after + 1, key, item)
else:
previous_item = self._body[-1][1]
if (
not isinstance(previous_item, Whitespace)
and not is_table
and "\n" not in previous_item.trivia.trail
if not (
isinstance(previous_item, Whitespace)
or ends_with_withespace(previous_item)
or is_table
or "\n" in previous_item.trivia.trail
):
previous_item.trivia.trail += "\n"
else:
@@ -250,9 +327,10 @@ class Container(MutableMapping, dict):
return self
def remove(self, key): # type: (Union[Key, str]) -> Container
def remove(self, key: Union[Key, str]) -> "Container":
"""Remove a key from the container."""
if not isinstance(key, Key):
key = Key(key)
key = SingleKey(key)
idx = self._map.pop(key, None)
if idx is None:
@@ -269,8 +347,8 @@ class Container(MutableMapping, dict):
return self
def _insert_after(
self, key, other_key, item
): # type: (Union[str, Key], Union[str, Key], Union[Item, Any]) -> Container
self, key: Union[Key, str], other_key: Union[Key, str], item: Any
) -> "Container":
if key is None:
raise ValueError("Key cannot be null in insert_after()")
@@ -278,10 +356,10 @@ class Container(MutableMapping, dict):
raise NonExistentKey(key)
if not isinstance(key, Key):
key = Key(key)
key = SingleKey(key)
if not isinstance(other_key, Key):
other_key = Key(other_key)
other_key = SingleKey(other_key)
item = _item(item)
@@ -315,23 +393,22 @@ class Container(MutableMapping, dict):
return self
def _insert_at(
self, idx, key, item
): # type: (int, Union[str, Key], Union[Item, Any]) -> Container
def _insert_at(self, idx: int, key: Union[Key, str], item: Any) -> "Container":
if idx > len(self._body) - 1:
raise ValueError("Unable to insert at position {}".format(idx))
raise ValueError(f"Unable to insert at position {idx}")
if not isinstance(key, Key):
key = Key(key)
key = SingleKey(key)
item = _item(item)
if idx > 0:
previous_item = self._body[idx - 1][1]
if (
not isinstance(previous_item, Whitespace)
and not isinstance(item, (AoT, Table))
and "\n" not in previous_item.trivia.trail
if not (
isinstance(previous_item, Whitespace)
or ends_with_withespace(previous_item)
or isinstance(item, (AoT, Table))
or "\n" in previous_item.trivia.trail
):
previous_item.trivia.trail += "\n"
@@ -357,9 +434,10 @@ class Container(MutableMapping, dict):
return self
def item(self, key): # type: (Union[Key, str]) -> Item
def item(self, key: Union[Key, str]) -> Item:
"""Get an item for the given key."""
if not isinstance(key, Key):
key = Key(key)
key = SingleKey(key)
idx = self._map.get(key, None)
if idx is None:
@@ -373,11 +451,13 @@ class Container(MutableMapping, dict):
return self._body[idx][1]
def last_item(self): # type: () -> Optional[Item]
def last_item(self) -> Optional[Item]:
"""Get the last item."""
if self._body:
return self._body[-1][1]
def as_string(self): # type: () -> str
def as_string(self) -> str:
"""Render as TOML string."""
s = ""
for k, v in self._body:
if k is not None:
@@ -393,8 +473,8 @@ class Container(MutableMapping, dict):
return s
def _render_table(
self, key, table, prefix=None
): # (Key, Table, Optional[str]) -> str
self, key: Key, table: Table, prefix: Optional[str] = None
) -> str:
cur = ""
if table.display_name is not None:
@@ -457,7 +537,7 @@ class Container(MutableMapping, dict):
return cur
def _render_aot_table(self, table, prefix=None): # (Table, Optional[str]) -> str
def _render_aot_table(self, table: Table, prefix: Optional[str] = None) -> str:
cur = ""
_key = prefix or ""
@@ -510,43 +590,16 @@ class Container(MutableMapping, dict):
item.trivia.trail,
)
def __len__(self) -> int:
return dict.__len__(self)
def __iter__(self) -> Iterator[str]:
return iter(dict.keys(self))
# Dictionary methods
def pop(self, key, default=_NOT_SET):
try:
value = self[key]
except KeyError:
if default is _NOT_SET:
raise
return default
del self[key]
return value
def setdefault(
self, key, default=None
): # type: (Union[Key, str], Any) -> Union[Item, Container]
super(Container, self).setdefault(key, default=default)
return self[key]
def __contains__(self, key): # type: (Union[Key, str]) -> bool
def __getitem__(self, key: Union[Key, str]) -> Union[Item, "Container"]:
if not isinstance(key, Key):
key = Key(key)
return key in self._map
def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None
if key is not None and key in self:
self._replace(key, key, value)
else:
self.append(key, value)
def __getitem__(self, key): # type: (Union[Key, str]) -> Union[Item, Container]
if not isinstance(key, Key):
key = Key(key)
key = SingleKey(key)
idx = self._map.get(key, None)
if idx is None:
@@ -564,29 +617,25 @@ class Container(MutableMapping, dict):
return item
def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None
def __setitem__(self, key: Union[Key, str], value: Any) -> None:
if key is not None and key in self:
self._replace(key, key, value)
old_key = next(filter(lambda k: k == key, self._map))
self._replace(old_key, key, value)
else:
self.append(key, value)
def __delitem__(self, key): # type: (Union[Key, str]) -> None
def __delitem__(self, key: Union[Key, str]) -> None:
self.remove(key)
def __len__(self): # type: () -> int
return dict.__len__(self)
def __iter__(self): # type: () -> Iterator[str]
return iter(dict.keys(self))
def setdefault(self, key: Union[Key, str], default: Any) -> Any:
super().setdefault(key, default=default)
return self[key]
def _replace(
self, key, new_key, value
): # type: (Union[Key, str], Union[Key, str], Item) -> None
self, key: Union[Key, str], new_key: Union[Key, str], value: Item
) -> None:
if not isinstance(key, Key):
key = Key(key)
if not isinstance(new_key, Key):
new_key = Key(new_key)
key = SingleKey(key)
idx = self._map.get(key, None)
if idx is None:
@@ -595,10 +644,9 @@ class Container(MutableMapping, dict):
self._replace_at(idx, new_key, value)
def _replace_at(
self, idx, new_key, value
): # type: (Union[int, Tuple[int]], Union[Key, str], Item) -> None
if not isinstance(new_key, Key):
new_key = Key(new_key)
self, idx: Union[int, Tuple[int]], new_key: Union[Key, str], value: Item
) -> None:
value = _item(value)
if isinstance(idx, tuple):
for i in idx[1:]:
@@ -607,38 +655,63 @@ class Container(MutableMapping, dict):
idx = idx[0]
k, v = self._body[idx]
if not isinstance(new_key, Key):
if (
isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table))
or new_key != k.key
):
new_key = SingleKey(new_key)
else: # Inherit the sep of the old key
new_key = k
self._map[new_key] = self._map.pop(k)
del self._map[k]
self._map[new_key] = idx
if new_key != k:
dict.__delitem__(self, k)
if isinstance(self._map[new_key], tuple):
self._map[new_key] = self._map[new_key][0]
if isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)):
# new tables should appear after all non-table values
self.remove(k)
for i in range(idx, len(self._body)):
if isinstance(self._body[i][1], (AoT, Table)):
self._insert_at(i, new_key, value)
idx = i
break
else:
idx = -1
self.append(new_key, value)
else:
# Copying trivia
if not isinstance(value, (Whitespace, AoT)):
value.trivia.indent = v.trivia.indent
value.trivia.comment_ws = value.trivia.comment_ws or v.trivia.comment_ws
value.trivia.comment = value.trivia.comment or v.trivia.comment
value.trivia.trail = v.trivia.trail
self._body[idx] = (new_key, value)
value = _item(value)
# Copying trivia
if not isinstance(value, (Whitespace, AoT)):
value.trivia.indent = v.trivia.indent
value.trivia.comment_ws = v.trivia.comment_ws
value.trivia.comment = v.trivia.comment
value.trivia.trail = v.trivia.trail
if hasattr(value, "invalidate_display_name"):
value.invalidate_display_name() # type: ignore[attr-defined]
if isinstance(value, Table):
# Insert a cosmetic new line for tables
value.append(None, Whitespace("\n"))
# Insert a cosmetic new line for tables if:
# - it does not have it yet OR is not followed by one
# - it is not the last item
last, _ = self._previous_item_with_index()
idx = last if idx < 0 else idx
has_ws = ends_with_withespace(value)
next_ws = idx < last and isinstance(self._body[idx + 1][1], Whitespace)
if idx < last and not (next_ws or has_ws):
value.append(None, Whitespace("\n"))
self._body[idx] = (new_key, value)
dict.__setitem__(self, new_key.key, value.value)
dict.__setitem__(self, new_key.key, value.value)
def __str__(self): # type: () -> str
def __str__(self) -> str:
return str(self.value)
def __repr__(self): # type: () -> str
def __repr__(self) -> str:
return repr(self.value)
def __eq__(self, other): # type: (Dict) -> bool
def __eq__(self, other: dict) -> bool:
if not isinstance(other, dict):
return NotImplemented
@@ -667,10 +740,10 @@ class Container(MutableMapping, dict):
if key is not None:
dict.__setitem__(self, key.key, item.value)
def copy(self): # type: () -> Container
def copy(self) -> "Container":
return copy.copy(self)
def __copy__(self): # type: () -> Container
def __copy__(self) -> "Container":
c = self.__class__(self._parsed)
for k, v in dict.items(self):
dict.__setitem__(c, k, v)
@@ -680,14 +753,34 @@ class Container(MutableMapping, dict):
return c
def _previous_item_with_index(
self, idx: Optional[int] = None, ignore=(Null,)
) -> Optional[Tuple[int, Item]]:
"""Find the immediate previous item before index ``idx``"""
if idx is None or idx > len(self._body):
idx = len(self._body)
for i in range(idx - 1, -1, -1):
v = self._body[i][-1]
if not isinstance(v, ignore):
return i, v
return None
class OutOfOrderTableProxy(MutableMapping, dict):
def __init__(self, container, indices): # type: (Container, Tuple) -> None
def _previous_item(
self, idx: Optional[int] = None, ignore=(Null,)
) -> Optional[Item]:
"""Find the immediate previous item before index ``idx``.
If ``idx`` is not given, the last item is returned.
"""
prev = self._previous_item_with_index(idx, ignore)
return prev[-1] if prev else None
class OutOfOrderTableProxy(_CustomDict):
def __init__(self, container: Container, indices: Tuple[int]) -> None:
self._container = container
self._internal_container = Container(self._container.parsing)
self._internal_container = Container(True)
self._tables = []
self._tables_map = {}
self._map = {}
for i in indices:
key, item = self._container._body[i]
@@ -700,27 +793,19 @@ class OutOfOrderTableProxy(MutableMapping, dict):
self._tables_map[k] = table_idx
if k is not None:
dict.__setitem__(self, k.key, v)
else:
self._internal_container.append(key, item)
self._map[key] = i
if key is not None:
dict.__setitem__(self, key.key, item)
@property
def value(self):
return self._internal_container.value
def __getitem__(self, key): # type: (Union[Key, str]) -> Any
def __getitem__(self, key: Union[Key, str]) -> Any:
if key not in self._internal_container:
raise NonExistentKey(key)
return self._internal_container[key]
def __setitem__(self, key, item): # type: (Union[Key, str], Any) -> None
if key in self._map:
idx = self._map[key]
self._container._replace_at(idx, key, item)
elif key in self._tables_map:
def __setitem__(self, key: Union[Key, str], item: Any) -> None:
if key in self._tables_map:
table = self._tables[self._tables_map[key]]
table[key] = item
elif self._tables:
@@ -729,15 +814,12 @@ class OutOfOrderTableProxy(MutableMapping, dict):
else:
self._container[key] = item
self._internal_container[key] = item
if key is not None:
dict.__setitem__(self, key, item)
def __delitem__(self, key): # type: (Union[Key, str]) -> None
if key in self._map:
idx = self._map[key]
del self._container[key]
del self._map[key]
elif key in self._tables_map:
def __delitem__(self, key: Union[Key, str]) -> None:
if key in self._tables_map:
table = self._tables[self._tables_map[key]]
del table[key]
del self._tables_map[key]
@@ -745,47 +827,27 @@ class OutOfOrderTableProxy(MutableMapping, dict):
raise NonExistentKey(key)
del self._internal_container[key]
if key is not None:
dict.__delitem__(self, key)
def keys(self):
return self._internal_container.keys()
def __iter__(self) -> Iterator[str]:
return iter(dict.keys(self))
def values(self):
return self._internal_container.values()
def items(self): # type: () -> Generator[Item]
return self._internal_container.items()
def update(self, other): # type: (Dict) -> None
self._internal_container.update(other)
def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any
return self._internal_container.get(key, default=default)
def pop(self, key, default=_NOT_SET):
return self._internal_container.pop(key, default=default)
def setdefault(
self, key, default=None
): # type: (Union[Key, str], Any) -> Union[Item, Container]
return self._internal_container.setdefault(key, default=default)
def __contains__(self, key):
return key in self._internal_container
def __iter__(self): # type: () -> Iterator[str]
return iter(self._internal_container)
def __str__(self):
return str(self._internal_container)
def __repr__(self):
return repr(self._internal_container)
def __eq__(self, other): # type: (Dict) -> bool
if not isinstance(other, dict):
return NotImplemented
return self._internal_container == other
def __len__(self) -> int:
return dict.__len__(self)
def __getattr__(self, attribute):
return getattr(self._internal_container, attribute)
def setdefault(self, key: Union[Key, str], default: Any) -> Any:
super().setdefault(key, default=default)
return self[key]
def ends_with_withespace(it: Any) -> bool:
"""Returns ``True`` if the given item ``it`` is a ``Table`` or ``AoT`` object
ending with a ``Whitespace``.
"""
return (
isinstance(it, Table) and isinstance(it.value._previous_item(), Whitespace)
) or (isinstance(it, AoT) and len(it) > 0 and isinstance(it[-1], Whitespace))
+38 -46
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from typing import Optional
@@ -15,18 +13,14 @@ class ParseError(ValueError, TOMLKitError):
location within the line where the error was encountered.
"""
def __init__(
self, line, col, message=None
): # type: (int, int, Optional[str]) -> None
def __init__(self, line: int, col: int, message: Optional[str] = None) -> None:
self._line = line
self._col = col
if message is None:
message = "TOML parse error"
super(ParseError, self).__init__(
"{} at line {} col {}".format(message, self._line, self._col)
)
super().__init__(f"{message} at line {self._line} col {self._col}")
@property
def line(self):
@@ -42,10 +36,10 @@ class MixedArrayTypesError(ParseError):
An array was found that had two or more element types.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Mixed types found in array"
super(MixedArrayTypesError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class InvalidNumberError(ParseError):
@@ -53,10 +47,10 @@ class InvalidNumberError(ParseError):
A numeric field was improperly specified.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Invalid number"
super(InvalidNumberError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class InvalidDateTimeError(ParseError):
@@ -64,10 +58,10 @@ class InvalidDateTimeError(ParseError):
A datetime field was improperly specified.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Invalid datetime"
super(InvalidDateTimeError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class InvalidDateError(ParseError):
@@ -75,10 +69,10 @@ class InvalidDateError(ParseError):
A date field was improperly specified.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Invalid date"
super(InvalidDateError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class InvalidTimeError(ParseError):
@@ -86,10 +80,10 @@ class InvalidTimeError(ParseError):
A date field was improperly specified.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Invalid time"
super(InvalidTimeError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class InvalidNumberOrDateError(ParseError):
@@ -97,10 +91,10 @@ class InvalidNumberOrDateError(ParseError):
A numeric or date field was improperly specified.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Invalid number or date format"
super(InvalidNumberOrDateError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class InvalidUnicodeValueError(ParseError):
@@ -108,10 +102,10 @@ class InvalidUnicodeValueError(ParseError):
A unicode code was improperly specified.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Invalid unicode value"
super(InvalidUnicodeValueError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class UnexpectedCharError(ParseError):
@@ -119,10 +113,10 @@ class UnexpectedCharError(ParseError):
An unexpected character was found during parsing.
"""
def __init__(self, line, col, char): # type: (int, int, str) -> None
message = "Unexpected character: {}".format(repr(char))
def __init__(self, line: int, col: int, char: str) -> None:
message = f"Unexpected character: {repr(char)}"
super(UnexpectedCharError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class EmptyKeyError(ParseError):
@@ -130,10 +124,10 @@ class EmptyKeyError(ParseError):
An empty key was found during parsing.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Empty key"
super(EmptyKeyError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class EmptyTableNameError(ParseError):
@@ -141,10 +135,10 @@ class EmptyTableNameError(ParseError):
An empty table name was found during parsing.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Empty table name"
super(EmptyTableNameError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class InvalidCharInStringError(ParseError):
@@ -152,10 +146,10 @@ class InvalidCharInStringError(ParseError):
The string being parsed contains an invalid character.
"""
def __init__(self, line, col, char): # type: (int, int, str) -> None
message = "Invalid character {} in string".format(repr(char))
def __init__(self, line: int, col: int, char: str) -> None:
message = f"Invalid character {repr(char)} in string"
super(InvalidCharInStringError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class UnexpectedEofError(ParseError):
@@ -163,10 +157,10 @@ class UnexpectedEofError(ParseError):
The TOML being parsed ended before the end of a statement.
"""
def __init__(self, line, col): # type: (int, int) -> None
def __init__(self, line: int, col: int) -> None:
message = "Unexpected end of file"
super(UnexpectedEofError, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
class InternalParserError(ParseError):
@@ -174,14 +168,12 @@ class InternalParserError(ParseError):
An error that indicates a bug in the parser.
"""
def __init__(
self, line, col, message=None
): # type: (int, int, Optional[str]) -> None
def __init__(self, line: int, col: int, message: Optional[str] = None) -> None:
msg = "Internal parser error"
if message:
msg += " ({})".format(message)
msg += f" ({message})"
super(InternalParserError, self).__init__(line, col, message=msg)
super().__init__(line, col, message=msg)
class NonExistentKey(KeyError, TOMLKitError):
@@ -190,9 +182,9 @@ class NonExistentKey(KeyError, TOMLKitError):
"""
def __init__(self, key):
message = 'Key "{}" does not exist.'.format(key)
message = f'Key "{key}" does not exist.'
super(NonExistentKey, self).__init__(message)
super().__init__(message)
class KeyAlreadyPresent(TOMLKitError):
@@ -201,23 +193,23 @@ class KeyAlreadyPresent(TOMLKitError):
"""
def __init__(self, key):
message = 'Key "{}" already exists.'.format(key)
message = f'Key "{key}" already exists.'
super(KeyAlreadyPresent, self).__init__(message)
super().__init__(message)
class InvalidControlChar(ParseError):
def __init__(self, line, col, char, type): # type: (int, int, int, str) -> None
def __init__(self, line: int, col: int, char: int, type: str) -> None:
display_code = "\\u00"
if char < 16:
display_code += "0"
display_code += str(char)
display_code += hex(char)[2:]
message = (
"Control characters (codes less than 0x1f and 0x7f) are not allowed in {}, "
"use {} instead".format(type, display_code)
)
super(InvalidControlChar, self).__init__(line, col, message=message)
super().__init__(line, col, message=message)
+733 -424
View File
File diff suppressed because it is too large Load Diff
+147 -321
View File
@@ -1,17 +1,12 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
import string
from typing import Any
from typing import Generator
from typing import List
from typing import Optional
from typing import Tuple
from typing import Type
from typing import Union
from ._compat import chr
from ._compat import decode
from ._utils import RFC_3339_LOOSE
from ._utils import _escaped
@@ -44,6 +39,7 @@ from .items import Item
from .items import Key
from .items import KeyType
from .items import Null
from .items import SingleKey
from .items import String
from .items import StringType
from .items import Table
@@ -67,11 +63,11 @@ class Parser:
Parser for TOML documents.
"""
def __init__(self, string): # type: (str) -> None
def __init__(self, string: str) -> None:
# Input to parse
self._src = Source(decode(string))
self._aot_stack = []
self._aot_stack: List[Key] = []
@property
def _state(self):
@@ -89,20 +85,20 @@ class Parser:
def _marker(self):
return self._src.marker
def extract(self): # type: () -> str
def extract(self) -> str:
"""
Extracts the value between marker and index
"""
return self._src.extract()
def inc(self, exception=None): # type: (Optional[ParseError.__class__]) -> bool
def inc(self, exception: Optional[Type[ParseError]] = None) -> bool:
"""
Increments the parser if the end of the input has not been reached.
Returns whether or not it was able to advance.
"""
return self._src.inc(exception=exception)
def inc_n(self, n, exception=None): # type: (int, Optional[ParseError]) -> bool
def inc_n(self, n: int, exception: Optional[Type[ParseError]] = None) -> bool:
"""
Increments the parser by n characters
if the end of the input has not been reached.
@@ -115,25 +111,25 @@ class Parser:
"""
return self._src.consume(chars=chars, min=min, max=max)
def end(self): # type: () -> bool
def end(self) -> bool:
"""
Returns True if the parser has reached the end of the input.
"""
return self._src.end()
def mark(self): # type: () -> None
def mark(self) -> None:
"""
Sets the marker to the index's current position
"""
self._src.mark()
def parse_error(self, exception=ParseError, *args):
def parse_error(self, exception=ParseError, *args, **kwargs):
"""
Creates a generic "parse error" at the current position.
"""
return self._src.parse_error(exception, *args)
return self._src.parse_error(exception, *args, **kwargs)
def parse(self): # type: () -> TOMLDocument
def parse(self) -> TOMLDocument:
body = TOMLDocument(True)
# Take all keyvals outside of tables/AoT's.
@@ -148,10 +144,8 @@ class Parser:
break
key, value = item
if key is not None and key.is_dotted():
if (key is not None and key.is_multi()) or not self._merge_ws(value, body):
# We actually have a table
self._handle_dotted_key(body, key, value)
elif not self._merge_ws(value, body):
body.append(key, value)
self.mark()
@@ -161,7 +155,7 @@ class Parser:
if isinstance(value, Table) and value.is_aot_element():
# This is just the first table in an AoT. Parse the rest of the array
# along with it.
value = self._parse_aot(value, key.key)
value = self._parse_aot(value, key)
body.append(key, value)
@@ -169,7 +163,7 @@ class Parser:
return body
def _merge_ws(self, item, container): # type: (Item, Container) -> bool
def _merge_ws(self, item: Item, container: Container) -> bool:
"""
Merges the given Item with the last one currently in the given Container if
both are whitespace items.
@@ -191,85 +185,20 @@ class Parser:
return True
def _is_child(self, parent, child): # type: (str, str) -> bool
def _is_child(self, parent: Key, child: Key) -> bool:
"""
Returns whether a key is strictly a child of another key.
AoT siblings are not considered children of one another.
"""
parent_parts = tuple(self._split_table_name(parent))
child_parts = tuple(self._split_table_name(child))
parent_parts = tuple(parent)
child_parts = tuple(child)
if parent_parts == child_parts:
return False
return parent_parts == child_parts[: len(parent_parts)]
def _split_table_name(self, name): # type: (str) -> Generator[Key]
in_name = False
current = ""
t = KeyType.Bare
parts = 0
for c in name:
c = TOMLChar(c)
if c == ".":
if in_name:
current += c
continue
if not current:
raise self.parse_error()
yield Key(current.strip(), t=t, sep="", original=current)
parts += 1
current = ""
t = KeyType.Bare
continue
elif c in {"'", '"'}:
if in_name:
if (
t == KeyType.Literal
and c == '"'
or t == KeyType.Basic
and c == "'"
):
current += c
continue
if c != t.value:
raise self.parse_error()
in_name = False
else:
if (
current.strip()
and TOMLChar(current[-1]).is_spaces()
and not parts
):
raise self.parse_error()
in_name = True
t = KeyType.Literal if c == "'" else KeyType.Basic
continue
elif in_name or c.is_bare_key_char():
current += c
elif c.is_spaces():
# A space is only valid at this point
# if it's in between parts.
# We store it for now and will check
# later if it's valid
current += c
continue
else:
raise self.parse_error()
if current.strip():
yield Key(current.strip(), t=t, sep="", original=current)
def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]]
def _parse_item(self) -> Optional[Tuple[Optional[Key], Item]]:
"""
Attempts to parse the next item and returns it, along with its key
if the item is value-like.
@@ -305,7 +234,7 @@ class Parser:
return self._parse_key_value(True)
def _parse_comment_trail(self): # type: () -> Tuple[str, str, str]
def _parse_comment_trail(self, parse_trail: bool = True) -> Tuple[str, str, str]:
"""
Returns (comment_ws, comment, trail)
If there is no comment, comment_ws and comment will
@@ -350,22 +279,23 @@ class Parser:
if self.end():
break
while self._current.is_spaces() and self.inc():
pass
if self._current == "\r":
self.inc()
if self._current == "\n":
self.inc()
trail = ""
if self._idx != self._marker or self._current.is_ws():
trail = self.extract()
if parse_trail:
while self._current.is_spaces() and self.inc():
pass
if self._current == "\r":
self.inc()
if self._current == "\n":
self.inc()
if self._idx != self._marker or self._current.is_ws():
trail = self.extract()
return comment_ws, comment, trail
def _parse_key_value(self, parse_comment=False): # type: (bool) -> (Key, Item)
def _parse_key_value(self, parse_comment: bool = False) -> Tuple[Key, Item]:
# Leading indent
self.mark()
@@ -386,7 +316,8 @@ class Parser:
raise self.parse_error(UnexpectedCharError, "=")
else:
found_equals = True
pass
if not found_equals:
raise self.parse_error(UnexpectedCharError, self._current)
if not key.sep:
key.sep = self.extract()
@@ -411,57 +342,53 @@ class Parser:
return key, val
def _parse_key(self): # type: () -> Key
def _parse_key(self) -> Key:
"""
Parses a Key at the current position;
WS before the key must be exhausted first at the callsite.
"""
self.mark()
while self._current.is_spaces() and self.inc():
# Skip any leading whitespace
pass
if self._current in "\"'":
return self._parse_quoted_key()
else:
return self._parse_bare_key()
def _parse_quoted_key(self): # type: () -> Key
def _parse_quoted_key(self) -> Key:
"""
Parses a key enclosed in either single or double quotes.
"""
# Extract the leading whitespace
original = self.extract()
quote_style = self._current
key_type = None
dotted = False
for t in KeyType:
if t.value == quote_style:
key_type = t
break
key_type = next((t for t in KeyType if t.value == quote_style), None)
if key_type is None:
raise RuntimeError("Should not have entered _parse_quoted_key()")
self.inc()
key_str = self._parse_string(
StringType.SLB if key_type == KeyType.Basic else StringType.SLL
)
if key_str._t.is_multiline():
raise self.parse_error(UnexpectedCharError, key_str._t.value)
original += key_str.as_string()
self.mark()
while self._current != quote_style and self.inc():
while self._current.is_spaces() and self.inc():
pass
key = self.extract()
original += self.extract()
key = SingleKey(str(key_str), t=key_type, sep="", original=original)
if self._current == ".":
self.inc()
dotted = True
key += "." + self._parse_key().as_string()
key_type = KeyType.Bare
else:
self.inc()
key = key.concat(self._parse_key())
return Key(key, key_type, "", dotted)
return key
def _parse_bare_key(self): # type: () -> Key
def _parse_bare_key(self) -> Key:
"""
Parses a bare key.
"""
key_type = None
dotted = False
self.mark()
while (
self._current.is_bare_key_char() or self._current.is_spaces()
) and self.inc():
@@ -471,85 +398,21 @@ class Parser:
key = original.strip()
if not key:
# Empty key
raise self.parse_error(ParseError, "Empty key found")
raise self.parse_error(EmptyKeyError)
if " " in key:
# Bare key with spaces in it
raise self.parse_error(ParseError, 'Invalid key "{}"'.format(key))
raise self.parse_error(ParseError, f'Invalid key "{key}"')
key = SingleKey(key, KeyType.Bare, "", original)
if self._current == ".":
self.inc()
dotted = True
original += "." + self._parse_key().as_string()
key = original.strip()
key_type = KeyType.Bare
key = key.concat(self._parse_key())
return Key(key, key_type, "", dotted, original=original)
return key
def _handle_dotted_key(
self, container, key, value
): # type: (Union[Container, Table], Key, Any) -> None
names = tuple(self._split_table_name(key.as_string()))
name = names[0]
name._dotted = True
if name in container:
if not isinstance(value, Table):
table = Table(Container(True), Trivia(), False, is_super_table=True)
_table = table
for i, _name in enumerate(names[1:]):
if i == len(names) - 2:
_name.sep = key.sep
_table.append(_name, value)
else:
_name._dotted = True
_table.append(
_name,
Table(
Container(True),
Trivia(),
False,
is_super_table=i < len(names) - 2,
),
)
_table = _table[_name]
value = table
container.append(name, value)
return
else:
table = Table(Container(True), Trivia(), False, is_super_table=True)
if isinstance(container, Table):
container.raw_append(name, table)
else:
container.append(name, table)
for i, _name in enumerate(names[1:]):
if i == len(names) - 2:
_name.sep = key.sep
table.append(_name, value)
else:
_name._dotted = True
if _name in table.value:
table = table.value[_name]
else:
table.append(
_name,
Table(
Container(True),
Trivia(),
False,
is_super_table=i < len(names) - 2,
),
)
table = table[_name]
def _parse_value(self): # type: () -> Item
def _parse_value(self) -> Item:
"""
Attempts to parse a value at the current position.
"""
@@ -674,7 +537,7 @@ class Parser:
def _parse_false(self):
return self._parse_bool(BoolType.FALSE)
def _parse_bool(self, style): # type: (BoolType) -> Bool
def _parse_bool(self, style: BoolType) -> Bool:
with self._state:
style = BoolType(style)
@@ -685,25 +548,25 @@ class Parser:
return Bool(style, Trivia())
def _parse_array(self): # type: () -> Array
def _parse_array(self) -> Array:
# Consume opening bracket, EOF here is an issue (middle of array)
self.inc(exception=UnexpectedEofError)
elems = [] # type: List[Item]
elems: List[Item] = []
prev_value = None
while True:
# consume whitespace
mark = self._idx
self.consume(TOMLChar.SPACES)
newline = self.consume(TOMLChar.NL)
self.consume(TOMLChar.SPACES + TOMLChar.NL)
indent = self._src[mark : self._idx]
newline = set(TOMLChar.NL) & set(indent)
if newline:
elems.append(Whitespace(indent))
continue
# consume comment
if self._current == "#":
cws, comment, trail = self._parse_comment_trail()
cws, comment, trail = self._parse_comment_trail(parse_trail=False)
elems.append(Comment(Trivia(indent, cws, comment, trail)))
continue
@@ -743,7 +606,7 @@ class Parser:
else:
return res
def _parse_inline_table(self): # type: () -> InlineTable
def _parse_inline_table(self) -> InlineTable:
# consume opening bracket, EOF here is an issue (middle of array)
self.inc(exception=UnexpectedEofError)
@@ -779,10 +642,7 @@ class Parser:
raise self.parse_error(UnexpectedCharError, self._current)
key, val = self._parse_key_value(False)
if key.is_dotted():
self._handle_dotted_key(elems, key, val)
else:
elems.add(key, val)
elems.add(key, val)
# consume trailing whitespace
mark = self._idx
@@ -799,17 +659,18 @@ class Parser:
return InlineTable(elems, Trivia())
def _parse_number(self, raw, trivia): # type: (str, Trivia) -> Optional[Item]
def _parse_number(self, raw: str, trivia: Trivia) -> Optional[Item]:
# Leading zeros are not allowed
sign = ""
if raw.startswith(("+", "-")):
sign = raw[0]
raw = raw[1:]
if (
len(raw) > 1
and raw.startswith("0")
if len(raw) > 1 and (
raw.startswith("0")
and not raw.startswith(("0.", "0o", "0x", "0b", "0e"))
or sign
and raw.startswith(".")
):
return
@@ -829,12 +690,16 @@ class Parser:
base = 16
# Underscores should be surrounded by digits
clean = re.sub("(?i)(?<={})_(?={})".format(digits, digits), "", raw)
clean = re.sub(f"(?i)(?<={digits})_(?={digits})", "", raw).lower()
if "_" in clean:
return
if clean.endswith("."):
if (
clean.endswith(".")
or not clean.startswith("0x")
and clean.split("e", 1)[0].endswith(".")
):
return
try:
@@ -845,11 +710,11 @@ class Parser:
except ValueError:
return
def _parse_literal_string(self): # type: () -> String
def _parse_literal_string(self) -> String:
with self._state:
return self._parse_string(StringType.SLL)
def _parse_basic_string(self): # type: () -> String
def _parse_basic_string(self) -> String:
with self._state:
return self._parse_string(StringType.SLB)
@@ -898,12 +763,12 @@ class Parser:
raise self.parse_error(InvalidCharInStringError, self._current)
def _parse_string(self, delim): # type: (StringType) -> String
def _parse_string(self, delim: StringType) -> String:
# only keep parsing for string if the current character matches the delim
if self._current != delim.unit:
raise self.parse_error(
InternalParserError,
"Invalid character for string type {}".format(delim),
f"Invalid character for string type {delim}",
)
# consume the opening/first delim, EOF here is an issue
@@ -1006,8 +871,8 @@ class Parser:
self.inc(exception=UnexpectedEofError)
def _parse_table(
self, parent_name=None, parent=None
): # type: (Optional[str], Optional[Table]) -> Tuple[Key, Union[Table, AoT]]
self, parent_name: Optional[Key] = None, parent: Optional[Table] = None
) -> Tuple[Key, Union[Table, AoT]]:
"""
Parses a table element.
"""
@@ -1028,58 +893,28 @@ class Parser:
raise self.parse_error(UnexpectedEofError)
is_aot = True
# Consume any whitespace
self.mark()
while self._current.is_spaces() and self.inc():
pass
ws_prefix = self.extract()
# Key
if self._current in [StringType.SLL.value, StringType.SLB.value]:
delimiter = (
StringType.SLL
if self._current == StringType.SLL.value
else StringType.SLB
)
name = self._parse_string(delimiter)
name = "{delimiter}{name}{delimiter}".format(
delimiter=delimiter.value, name=name
)
self.mark()
while self._current != "]" and self.inc():
if self.end():
raise self.parse_error(UnexpectedEofError)
pass
ws_suffix = self.extract()
name += ws_suffix
else:
self.mark()
while self._current != "]" and self.inc():
if self.end():
raise self.parse_error(UnexpectedEofError)
pass
name = self.extract()
name = ws_prefix + name
if not name.strip():
try:
key = self._parse_key()
except EmptyKeyError:
raise self.parse_error(EmptyTableNameError) from None
if self.end():
raise self.parse_error(UnexpectedEofError)
elif self._current != "]":
raise self.parse_error(UnexpectedCharError, self._current)
elif not key.key.strip():
raise self.parse_error(EmptyTableNameError)
key = Key(name, sep="")
name_parts = tuple(self._split_table_name(name))
key.sep = ""
full_key = key
name_parts = tuple(key)
if any(" " in part.key.strip() and part.is_bare() for part in name_parts):
raise self.parse_error(ParseError, 'Invalid table name "{}"'.format(name))
raise self.parse_error(
ParseError, f'Invalid table name "{full_key.as_string()}"'
)
missing_table = False
if parent_name:
parent_name_parts = tuple(self._split_table_name(parent_name))
parent_name_parts = tuple(parent_name)
else:
parent_name_parts = tuple()
@@ -1102,8 +937,8 @@ class Parser:
values,
Trivia(indent, cws, comment, trail),
is_aot,
name=name,
display_name=name,
name=name_parts[0].key if name_parts else key.key,
display_name=full_key.as_string(),
)
if len(name_parts) > 1:
@@ -1116,36 +951,36 @@ class Parser:
table = Table(
Container(True),
Trivia(indent, cws, comment, trail),
is_aot and name_parts[0].key in self._aot_stack,
is_aot and name_parts[0] in self._aot_stack,
is_super_table=True,
name=name_parts[0].key,
)
result = table
key = name_parts[0]
result = table
key = name_parts[0]
for i, _name in enumerate(name_parts[1:]):
if _name in table:
child = table[_name]
else:
child = Table(
Container(True),
Trivia(indent, cws, comment, trail),
is_aot and i == len(name_parts[1:]) - 1,
is_super_table=i < len(name_parts[1:]) - 1,
name=_name.key,
display_name=name if i == len(name_parts[1:]) - 1 else None,
)
for i, _name in enumerate(name_parts[1:]):
if _name in table:
child = table[_name]
else:
child = Table(
Container(True),
Trivia(indent, cws, comment, trail),
is_aot and i == len(name_parts) - 2,
is_super_table=i < len(name_parts) - 2,
name=_name.key,
display_name=full_key.as_string()
if i == len(name_parts) - 2
else None,
)
if is_aot and i == len(name_parts[1:]) - 1:
table.raw_append(
_name, AoT([child], name=table.name, parsed=True)
)
else:
table.raw_append(_name, child)
if is_aot and i == len(name_parts) - 2:
table.raw_append(_name, AoT([child], name=table.name, parsed=True))
else:
table.raw_append(_name, child)
table = child
values = table.value
table = child
values = table.value
else:
if name_parts:
key = name_parts[0]
@@ -1155,27 +990,24 @@ class Parser:
if item:
_key, item = item
if not self._merge_ws(item, values):
if _key is not None and _key.is_dotted():
self._handle_dotted_key(table, _key, item)
else:
table.raw_append(_key, item)
table.raw_append(_key, item)
else:
if self._current == "[":
is_aot_next, name_next = self._peek_table()
_, key_next = self._peek_table()
if self._is_child(name, name_next):
key_next, table_next = self._parse_table(name, table)
if self._is_child(full_key, key_next):
key_next, table_next = self._parse_table(full_key, table)
table.raw_append(key_next, table_next)
# Picking up any sibling
while not self.end():
_, name_next = self._peek_table()
_, key_next = self._peek_table()
if not self._is_child(name, name_next):
if not self._is_child(full_key, key_next):
break
key_next, table_next = self._parse_table(name, table)
key_next, table_next = self._parse_table(full_key, table)
table.raw_append(key_next, table_next)
@@ -1189,12 +1021,12 @@ class Parser:
if isinstance(result, Null):
result = table
if is_aot and (not self._aot_stack or name != self._aot_stack[-1]):
result = self._parse_aot(result, name)
if is_aot and (not self._aot_stack or full_key != self._aot_stack[-1]):
result = self._parse_aot(result, full_key)
return key, result
def _peek_table(self): # type: () -> Tuple[bool, str]
def _peek_table(self) -> Tuple[bool, Key]:
"""
Peeks ahead non-intrusively by cloning then restoring the
initial state of the parser.
@@ -1203,7 +1035,6 @@ class Parser:
as well as whether it is part of an AoT.
"""
# we always want to restore after exiting this scope
table_name = ""
with self._state(save_marker=True, restore=True):
if self._current != "[":
raise self.parse_error(
@@ -1217,15 +1048,12 @@ class Parser:
if self._current == "[":
self.inc()
is_aot = True
try:
return is_aot, self._parse_key()
except EmptyKeyError:
raise self.parse_error(EmptyTableNameError) from None
self.mark()
while self._current != "]" and self.inc():
table_name = self.extract()
return is_aot, table_name
def _parse_aot(self, first, name_first): # type: (Table, str) -> AoT
def _parse_aot(self, first: Table, name_first: Key) -> AoT:
"""
Parses all siblings of the provided table first and bundles them into
an AoT.
@@ -1244,7 +1072,7 @@ class Parser:
return AoT(payload, parsed=True)
def _peek(self, n): # type: (int) -> str
def _peek(self, n: int) -> str:
"""
Peeks ahead n characters.
@@ -1254,7 +1082,7 @@ class Parser:
with self._state(restore=True):
buf = ""
for _ in range(n):
if self._current not in " \t\n\r#,]}":
if self._current not in " \t\n\r#,]}" + self._src.EOF:
buf += self._current
self.inc()
continue
@@ -1262,9 +1090,7 @@ class Parser:
break
return buf
def _peek_unicode(
self, is_long
): # type: (bool) -> Tuple[Optional[str], Optional[str]]
def _peek_unicode(self, is_long: bool) -> Tuple[Optional[str], Optional[str]]:
"""
Peeks ahead non-intrusively by cloning then restoring the
initial state of the parser.
+29 -37
View File
@@ -1,39 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import itertools
from copy import copy
from typing import Any
from typing import Optional
from typing import Tuple
from typing import Type
from ._compat import PY2
from ._compat import unicode
from .exceptions import ParseError
from .exceptions import UnexpectedCharError
from .exceptions import UnexpectedEofError
from .toml_char import TOMLChar
class _State:
def __init__(
self, source, save_marker=False, restore=False
): # type: (_Source, Optional[bool], Optional[bool]) -> None
self,
source: "Source",
save_marker: Optional[bool] = False,
restore: Optional[bool] = False,
) -> None:
self._source = source
self._save_marker = save_marker
self.restore = restore
def __enter__(self): # type: () -> None
def __enter__(self) -> "_State":
# Entering this context manager - save the state
if PY2:
# Python 2.7 does not allow to directly copy
# an iterator, so we have to make tees of the original
# chars iterator.
self._source._chars, self._chars = itertools.tee(self._source._chars)
else:
self._chars = copy(self._source._chars)
self._chars = copy(self._source._chars)
self._idx = self._source._idx
self._current = self._source._current
self._marker = self._source._marker
@@ -55,14 +44,14 @@ class _StateHandler:
State preserver for the Parser.
"""
def __init__(self, source): # type: (Source) -> None
def __init__(self, source: "Source") -> None:
self._source = source
self._states = []
def __call__(self, *args, **kwargs):
return _State(self._source, *args, **kwargs)
def __enter__(self): # type: () -> None
def __enter__(self) -> None:
state = self()
self._states.append(state)
return state.__enter__()
@@ -72,11 +61,11 @@ class _StateHandler:
return state.__exit__(exception_type, exception_val, trace)
class Source(unicode):
class Source(str):
EOF = TOMLChar("\0")
def __init__(self, _): # type: (unicode) -> None
super(Source, self).__init__()
def __init__(self, _: str) -> None:
super().__init__()
# Collection of TOMLChars
self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self)])
@@ -97,28 +86,28 @@ class Source(unicode):
self.mark()
@property
def state(self): # type: () -> _StateHandler
def state(self) -> _StateHandler:
return self._state
@property
def idx(self): # type: () -> int
def idx(self) -> int:
return self._idx
@property
def current(self): # type: () -> TOMLChar
def current(self) -> TOMLChar:
return self._current
@property
def marker(self): # type: () -> int
def marker(self) -> int:
return self._marker
def extract(self): # type: () -> unicode
def extract(self) -> str:
"""
Extracts the value between marker and index
"""
return self[self._marker : self._idx]
def inc(self, exception=None): # type: (Optional[Type[ParseError]]) -> bool
def inc(self, exception: Optional[Type[ParseError]] = None) -> bool:
"""
Increments the parser if the end of the input has not been reached.
Returns whether or not it was able to advance.
@@ -135,7 +124,7 @@ class Source(unicode):
return False
def inc_n(self, n, exception=None): # type: (int, Exception) -> bool
def inc_n(self, n: int, exception: Optional[Type[ParseError]] = None) -> bool:
"""
Increments the parser by n characters
if the end of the input has not been reached.
@@ -158,31 +147,34 @@ class Source(unicode):
# failed to consume minimum number of characters
if min > 0:
self.parse_error(UnexpectedCharError)
raise self.parse_error(UnexpectedCharError, self.current)
def end(self): # type: () -> bool
def end(self) -> bool:
"""
Returns True if the parser has reached the end of the input.
"""
return self._current is self.EOF
def mark(self): # type: () -> None
def mark(self) -> None:
"""
Sets the marker to the index's current position
"""
self._marker = self._idx
def parse_error(
self, exception=ParseError, *args
): # type: (Type[ParseError], Any) -> ParseError
self,
exception: Type[ParseError] = ParseError,
*args: Any,
**kwargs: Any,
) -> ParseError:
"""
Creates a generic "parse error" at the current position.
"""
line, col = self._to_linecol()
return exception(line, col, *args)
return exception(line, col, *args, **kwargs)
def _to_linecol(self): # type: () -> Tuple[int, int]
def _to_linecol(self) -> Tuple[int, int]:
cur = 0
for i, line in enumerate(self.splitlines()):
if cur + len(line) + 1 > self.idx:
+9 -16
View File
@@ -1,18 +1,11 @@
import string
from ._compat import PY2
from ._compat import unicode
from functools import lru_cache
if PY2:
from pipenv.vendor.backports.functools_lru_cache import lru_cache
else:
from functools import lru_cache
class TOMLChar(unicode):
class TOMLChar(str):
def __init__(self, c):
super(TOMLChar, self).__init__()
super().__init__()
if len(self) > 1:
raise ValueError("A TOML character must be of length 1")
@@ -25,42 +18,42 @@ class TOMLChar(unicode):
WS = SPACES + NL
@lru_cache(maxsize=None)
def is_bare_key_char(self): # type: () -> bool
def is_bare_key_char(self) -> bool:
"""
Whether the character is a valid bare key name or not.
"""
return self in self.BARE
@lru_cache(maxsize=None)
def is_kv_sep(self): # type: () -> bool
def is_kv_sep(self) -> bool:
"""
Whether the character is a valid key/value separator ot not.
"""
return self in self.KV
@lru_cache(maxsize=None)
def is_int_float_char(self): # type: () -> bool
def is_int_float_char(self) -> bool:
"""
Whether the character if a valid integer or float value character or not.
"""
return self in self.NUMBER
@lru_cache(maxsize=None)
def is_ws(self): # type: () -> bool
def is_ws(self) -> bool:
"""
Whether the character is a whitespace character or not.
"""
return self in self.WS
@lru_cache(maxsize=None)
def is_nl(self): # type: () -> bool
def is_nl(self) -> bool:
"""
Whether the character is a new line character or not.
"""
return self in self.NL
@lru_cache(maxsize=None)
def is_spaces(self): # type: () -> bool
def is_spaces(self) -> bool:
"""
Whether the character is a space or not
"""
+10 -11
View File
@@ -1,24 +1,23 @@
import io
from typing import Any
from typing import Dict
from .api import loads
from .toml_document import TOMLDocument
class TOMLFile(object):
class TOMLFile:
"""
Represents a TOML file.
:param path: path to the TOML file
"""
def __init__(self, path): # type: (str) -> None
def __init__(self, path: str) -> None:
self._path = path
def read(self): # type: () -> TOMLDocument
with io.open(self._path, encoding="utf-8") as f:
def read(self) -> TOMLDocument:
"""Read the file content as a :class:`tomlkit.toml_document.TOMLDocument`."""
with open(self._path, encoding="utf-8", newline="") as f:
return loads(f.read())
def write(self, data): # type: (TOMLDocument) -> None
with io.open(self._path, "w", encoding="utf-8") as f:
def write(self, data: TOMLDocument) -> None:
"""Write the TOMLDocument to the file."""
with open(self._path, "w", encoding="utf-8", newline="") as f:
f.write(data.as_string())