From 8faa74cdc9da20cfdcc69f5ec29b91112c95b4c9 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Mon, 25 Jul 2022 17:18:43 +0200 Subject: [PATCH] Update tomlkit==0.9.2 Used: python -m invoke vendoring.update --package=tomlkit --- pipenv/vendor/tomlkit/__init__.py | 31 +- pipenv/vendor/tomlkit/_compat.py | 166 +--- pipenv/vendor/tomlkit/_utils.py | 37 +- pipenv/vendor/tomlkit/api.py | 184 ++++- pipenv/vendor/tomlkit/container.py | 444 +++++----- pipenv/vendor/tomlkit/exceptions.py | 84 +- pipenv/vendor/tomlkit/items.py | 1157 +++++++++++++++++---------- pipenv/vendor/tomlkit/parser.py | 468 ++++------- pipenv/vendor/tomlkit/source.py | 66 +- pipenv/vendor/tomlkit/toml_char.py | 25 +- pipenv/vendor/tomlkit/toml_file.py | 21 +- 11 files changed, 1423 insertions(+), 1260 deletions(-) diff --git a/pipenv/vendor/tomlkit/__init__.py b/pipenv/vendor/tomlkit/__init__.py index 110d6c19..84566d45 100644 --- a/pipenv/vendor/tomlkit/__init__.py +++ b/pipenv/vendor/tomlkit/__init__.py @@ -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", +] diff --git a/pipenv/vendor/tomlkit/_compat.py b/pipenv/vendor/tomlkit/_compat.py index 487ed990..1295df69 100644 --- a/pipenv/vendor/tomlkit/_compat.py +++ b/pipenv/vendor/tomlkit/_compat.py @@ -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"] diff --git a/pipenv/vendor/tomlkit/_utils.py b/pipenv/vendor/tomlkit/_utils.py index 2ae3e424..f3fa49ff 100644 --- a/pipenv/vendor/tomlkit/_utils.py +++ b/pipenv/vendor/tomlkit/_utils.py @@ -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] diff --git a/pipenv/vendor/tomlkit/api.py b/pipenv/vendor/tomlkit/api.py index 3de41219..c4cc07ff 100644 --- a/pipenv/vendor/tomlkit/api.py +++ b/pipenv/vendor/tomlkit/api.py @@ -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)) diff --git a/pipenv/vendor/tomlkit/container.py b/pipenv/vendor/tomlkit/container.py index e0976368..c20a6ab3 100644 --- a/pipenv/vendor/tomlkit/container.py +++ b/pipenv/vendor/tomlkit/container.py @@ -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)) diff --git a/pipenv/vendor/tomlkit/exceptions.py b/pipenv/vendor/tomlkit/exceptions.py index d0c7ab5a..66370dbc 100644 --- a/pipenv/vendor/tomlkit/exceptions.py +++ b/pipenv/vendor/tomlkit/exceptions.py @@ -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) diff --git a/pipenv/vendor/tomlkit/items.py b/pipenv/vendor/tomlkit/items.py index 9e746a8c..abbb05fd 100644 --- a/pipenv/vendor/tomlkit/items.py +++ b/pipenv/vendor/tomlkit/items.py @@ -1,35 +1,76 @@ -from __future__ import unicode_literals - +import abc +import copy import re import string from datetime import date from datetime import datetime from datetime import time +from datetime import tzinfo from enum import Enum +from functools import lru_cache +from typing import TYPE_CHECKING from typing import Any from typing import Dict -from typing import Generator +from typing import Iterable +from typing import Iterator from typing import List from typing import Optional +from typing import TypeVar from typing import Union +from typing import cast +from typing import overload -from ._compat import PY2 from ._compat import PY38 -from ._compat import MutableMapping from ._compat import decode -from ._compat import long -from ._compat import unicode from ._utils import escape_string +from .toml_char import TOMLChar -if PY2: - from pipenv.vendor.backports.functools_lru_cache import lru_cache +if TYPE_CHECKING: # pragma: no cover + # Define _CustomList and _CustomDict as a workaround for: + # https://github.com/python/mypy/issues/11427 + # + # According to this issue, the typeshed contains a "lie" + # (it adds MutableSequence to the ancestry of list and MutableMapping to + # the ancestry of dict) which completely messes with the type inference for + # Table, InlineTable, Array and Container. + # + # Importing from builtins is preferred over simple assignment, see issues: + # https://github.com/python/mypy/issues/8715 + # https://github.com/python/mypy/issues/10068 + from builtins import dict as _CustomDict + from builtins import list as _CustomList + + # Allow type annotations but break circular imports + from . import container else: - from functools import lru_cache + from collections.abc import MutableMapping + from collections.abc import MutableSequence + + class _CustomList(MutableSequence, list): + """Adds MutableSequence mixin while pretending to be a builtin list""" + + class _CustomDict(MutableMapping, dict): + """Adds MutableMapping mixin while pretending to be a builtin dict""" -def item(value, _parent=None, _sort_keys=False): +def item( + value: Any, _parent: Optional["Item"] = 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 .container import Container if isinstance(value, Item): @@ -42,40 +83,51 @@ def item(value, _parent=None, _sort_keys=False): elif isinstance(value, float): return Float(value, Trivia(), str(value)) elif isinstance(value, dict): - val = Table(Container(), Trivia(), False) + table_constructor = InlineTable if isinstance(_parent, Array) 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) + only_child = len(value) == 1 and val[next(iter(value))] + if ( + table_constructor is Table + and only_child + and isinstance(only_child, (AoT, Table)) + ): + # The table becomes super table if the only child is a table or AoT. + val._is_super_table = True return val - elif isinstance(value, (tuple, list)): - if value and isinstance(value[0], dict): + elif isinstance(value, (list, tuple)): + if value and all(isinstance(v, dict) for v in value): a = AoT([]) + table_constructor = Table else: a = Array([], Trivia()) + table_constructor = InlineTable for v in value: if isinstance(v, dict): - table = Table(Container(), Trivia(), True) + 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, _sort_keys=_sort_keys) + i = item(_v, _parent=a, _sort_keys=_sort_keys) if isinstance(table, InlineTable): i.trivia.trail = "" - table[k] = item(i, _sort_keys=_sort_keys) + table[k] = i v = table a.append(v) return a - elif isinstance(value, (str, unicode)): + elif isinstance(value, str): escaped = escape_string(value) return String(StringType.SLB, decode(value), escaped, Trivia()) @@ -105,7 +157,7 @@ def item(value, _parent=None, _sort_keys=False): value.isoformat(), ) - raise ValueError("Invalid type {}".format(type(value))) + raise ValueError(f"Invalid type {type(value)}") class StringType(Enum): @@ -120,27 +172,27 @@ class StringType(Enum): @property @lru_cache(maxsize=None) - def unit(self): # type: () -> str + def unit(self) -> str: return self.value[0] @lru_cache(maxsize=None) - def is_basic(self): # type: () -> bool + def is_basic(self) -> bool: return self in {StringType.SLB, StringType.MLB} @lru_cache(maxsize=None) - def is_literal(self): # type: () -> bool + def is_literal(self) -> bool: return self in {StringType.SLL, StringType.MLL} @lru_cache(maxsize=None) - def is_singleline(self): # type: () -> bool + def is_singleline(self) -> bool: return self in {StringType.SLB, StringType.SLL} @lru_cache(maxsize=None) - def is_multiline(self): # type: () -> bool + def is_multiline(self) -> bool: return self in {StringType.MLB, StringType.MLL} @lru_cache(maxsize=None) - def toggle(self): # type: () -> StringType + def toggle(self) -> "StringType": return { StringType.SLB: StringType.MLB, StringType.MLB: StringType.SLB, @@ -157,9 +209,6 @@ class BoolType(Enum): def __bool__(self): return {BoolType.TRUE: True, BoolType.FALSE: False}[self] - if PY2: - __nonzero__ = __bool__ # for PY2 - def __iter__(self): return iter(self.value) @@ -173,8 +222,12 @@ class Trivia: """ def __init__( - self, indent=None, comment_ws=None, comment=None, trail=None - ): # type: (str, str, str, str) -> None + self, + indent: str = None, + comment_ws: str = None, + comment: str = None, + trail: str = None, + ) -> None: # Whitespace before a value. self.indent = indent or "" # Whitespace after a value, but before a comment. @@ -187,6 +240,9 @@ class Trivia: self.trail = trail + def copy(self) -> "Trivia": + return type(self)(self.indent, self.comment_ws, self.comment, self.trail) + class KeyType(Enum): """ @@ -201,17 +257,63 @@ class KeyType(Enum): Literal = "'" -class Key: - """ - A key value. - """ +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"" + + +class SingleKey(Key): + """A single key""" def __init__( - self, k, t=None, sep=None, dotted=False, original=None - ): # type: (str, Optional[KeyType], Optional[str], bool, Optional[str]) -> None + self, + k: str, + t: Optional[KeyType] = None, + sep: Optional[str] = None, + original: Optional[str] = None, + ) -> None: if t is None: - if any( - [c not in string.ascii_letters + string.digits + "-" + "_" for c in k] + if not k or any( + c not in string.ascii_letters + string.digits + "-" + "_" for c in k ): t = KeyType.Basic else: @@ -224,63 +326,80 @@ class Key: self.sep = sep self.key = k if original is None: - original = k + key_str = escape_string(k) if t == KeyType.Basic else k + original = f"{t.value}{key_str}{t.value}" self._original = original - - self._dotted = dotted + self._keys = [self] + self._dotted = False @property - def delimiter(self): # type: () -> str + def delimiter(self) -> str: + """The delimiter: double quote/single quote/none""" return self.t.value - def is_dotted(self): # type: () -> bool - return self._dotted - - def is_bare(self): # type: () -> bool + def is_bare(self) -> bool: + """Check if the key is bare""" return self.t == KeyType.Bare - def as_string(self): # type: () -> str - return "{}{}{}".format(self.delimiter, self._original, self.delimiter) - - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash(self.key) - def __eq__(self, other): # type: (Key) -> bool + def __eq__(self, other: Any) -> bool: if isinstance(other, Key): - return self.key == other.key + return isinstance(other, SingleKey) and self.key == other.key return self.key == other - def __str__(self): # type: () -> str - return self.as_string() - def __repr__(self): # type: () -> str - return "".format(self.as_string()) +class DottedKey(Key): + def __init__( + self, + keys: Iterable[Key], + sep: Optional[str] = None, + original: Optional[str] = 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(object): +class Item: """ An item within a TOML document. """ - def __init__(self, trivia): # type: (Trivia) -> None + def __init__(self, trivia: Trivia) -> None: self._trivia = trivia @property - def trivia(self): # type: () -> Trivia + def trivia(self) -> Trivia: + """The trivia element associated with this item""" return self._trivia @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: raise NotImplementedError() - def as_string(self): # type: () -> str + def as_string(self) -> str: + """The TOML representation""" raise NotImplementedError() # Helpers - def comment(self, comment): # type: (str) -> Item + def comment(self, comment: str) -> "Item": + """Attach a comment to this item""" if not comment.strip().startswith("#"): comment = "# " + comment @@ -289,7 +408,8 @@ class Item(object): return self - def indent(self, indent): # type: (int) -> Item + 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: @@ -297,16 +417,16 @@ class Item(object): return self - def is_boolean(self): # type: () -> bool + def is_boolean(self) -> bool: return isinstance(self, Bool) - def is_table(self): # type: () -> bool + def is_table(self) -> bool: return isinstance(self, Table) - def is_inline_table(self): # type: () -> bool + def is_inline_table(self) -> bool: return isinstance(self, InlineTable) - def is_aot(self): # type: () -> bool + def is_aot(self) -> bool: return isinstance(self, AoT) def _getstate(self, protocol=3): @@ -324,34 +444,36 @@ class Whitespace(Item): A whitespace literal. """ - def __init__(self, s, fixed=False): # type: (str, bool) -> None + def __init__(self, s: str, fixed: bool = False) -> None: self._s = s self._fixed = fixed @property - def s(self): # type: () -> str + def s(self) -> str: return self._s @property - def value(self): # type: () -> str + def value(self) -> str: + """The wrapped string of the whitespace""" return self._s @property - def trivia(self): # type: () -> Trivia + def trivia(self) -> Trivia: raise RuntimeError("Called trivia on a Whitespace variant.") @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 0 - def is_fixed(self): # type: () -> bool + 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): # type: () -> str + def as_string(self) -> str: return self._s - def __repr__(self): # type: () -> str - return "<{} {}>".format(self.__class__.__name__, repr(self._s)) + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {repr(self._s)}>" def _getstate(self, protocol=3): return self._s, self._fixed @@ -363,28 +485,28 @@ class Comment(Item): """ @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 1 - def as_string(self): # type: () -> str + def as_string(self) -> str: return "{}{}{}".format( self._trivia.indent, decode(self._trivia.comment), self._trivia.trail ) - def __str__(self): # type: () -> str - return "{}{}".format(self._trivia.indent, decode(self._trivia.comment)) + def __str__(self) -> str: + return f"{self._trivia.indent}{decode(self._trivia.comment)}" -class Integer(long, Item): +class Integer(int, Item): """ An integer literal. """ - def __new__(cls, value, trivia, raw): # type: (int, Trivia, str) -> Integer - return super(Integer, cls).__new__(cls, value) + def __new__(cls, value: int, trivia: Trivia, raw: str) -> "Integer": + return super().__new__(cls, value) - def __init__(self, _, trivia, raw): # type: (int, Trivia, str) -> None - super(Integer, self).__init__(trivia) + def __init__(self, _: int, trivia: Trivia, raw: str) -> None: + super().__init__(trivia) self._raw = raw self._sign = False @@ -393,23 +515,24 @@ class Integer(long, Item): self._sign = True @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 2 @property - def value(self): # type: () -> int + def value(self) -> int: + """The wrapped integer value""" return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): - result = super(Integer, self).__add__(other) + result = super().__add__(other) return self._new(result) def __radd__(self, other): - result = super(Integer, self).__radd__(other) + result = super().__radd__(other) if isinstance(other, Integer): return self._new(result) @@ -417,12 +540,12 @@ class Integer(long, Item): return result def __sub__(self, other): - result = super(Integer, self).__sub__(other) + result = super().__sub__(other) return self._new(result) def __rsub__(self, other): - result = super(Integer, self).__rsub__(other) + result = super().__rsub__(other) if isinstance(other, Integer): return self._new(result) @@ -447,11 +570,11 @@ class Float(float, Item): A float literal. """ - def __new__(cls, value, trivia, raw): # type: (float, Trivia, str) -> Integer - return super(Float, cls).__new__(cls, value) + def __new__(cls, value: float, trivia: Trivia, raw: str) -> Integer: + return super().__new__(cls, value) - def __init__(self, _, trivia, raw): # type: (float, Trivia, str) -> None - super(Float, self).__init__(trivia) + def __init__(self, _: float, trivia: Trivia, raw: str) -> None: + super().__init__(trivia) self._raw = raw self._sign = False @@ -460,23 +583,24 @@ class Float(float, Item): self._sign = True @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 3 @property - def value(self): # type: () -> float + def value(self) -> float: + """The wrapped float value""" return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): - result = super(Float, self).__add__(other) + result = super().__add__(other) return self._new(result) def __radd__(self, other): - result = super(Float, self).__radd__(other) + result = super().__radd__(other) if isinstance(other, Float): return self._new(result) @@ -484,12 +608,12 @@ class Float(float, Item): return result def __sub__(self, other): - result = super(Float, self).__sub__(other) + result = super().__sub__(other) return self._new(result) def __rsub__(self, other): - result = super(Float, self).__rsub__(other) + result = super().__rsub__(other) if isinstance(other, Float): return self._new(result) @@ -514,20 +638,21 @@ class Bool(Item): A boolean literal. """ - def __init__(self, t, trivia): # type: (int, Trivia) -> None - super(Bool, self).__init__(trivia) + def __init__(self, t: int, trivia: Trivia) -> None: + super().__init__(trivia) self._value = bool(t) @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 4 @property - def value(self): # type: () -> bool + def value(self) -> bool: + """The wrapped boolean value""" return self._value - def as_string(self): # type: () -> str + def as_string(self) -> str: return str(self._value).lower() def _getstate(self, protocol=3): @@ -558,18 +683,18 @@ class DateTime(Item, datetime): def __new__( cls, - year, - month, - day, - hour, - minute, - second, - microsecond, - tzinfo, - trivia, - raw, - **kwargs - ): # type: (int, int, int, int, int, int, int, Optional[datetime.tzinfo], Trivia, str, Any) -> datetime + year: int, + month: int, + day: int, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + trivia: Trivia, + raw: str, + **kwargs: Any, + ) -> datetime: return datetime.__new__( cls, year, @@ -580,25 +705,35 @@ class DateTime(Item, datetime): second, microsecond, tzinfo=tzinfo, - **kwargs + **kwargs, ) def __init__( - self, year, month, day, hour, minute, second, microsecond, tzinfo, trivia, raw - ): # type: (int, int, int, int, int, int, int, Optional[datetime.tzinfo], Trivia, str) -> None - super(DateTime, self).__init__(trivia) + self, + year: int, + month: int, + day: int, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + trivia: Trivia, + raw: str, + ) -> None: + super().__init__(trivia) self._raw = raw @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 5 @property - def value(self): # type: () -> datetime + def value(self) -> datetime: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): @@ -614,7 +749,7 @@ class DateTime(Item, datetime): self.tzinfo, ).__add__(other) else: - result = super(DateTime, self).__add__(other) + result = super().__add__(other) return self._new(result) @@ -631,7 +766,7 @@ class DateTime(Item, datetime): self.tzinfo, ).__sub__(other) else: - result = super(DateTime, self).__sub__(other) + result = super().__sub__(other) if isinstance(result, datetime): result = self._new(result) @@ -674,32 +809,32 @@ class Date(Item, date): A date literal. """ - def __new__(cls, year, month, day, *_): # type: (int, int, int, Any) -> date + def __new__(cls, year: int, month: int, day: int, *_: Any) -> date: return date.__new__(cls, year, month, day) def __init__( - self, year, month, day, trivia, raw - ): # type: (int, int, int, Trivia, str) -> None - super(Date, self).__init__(trivia) + self, year: int, month: int, day: int, trivia: Trivia, raw: str + ) -> None: + super().__init__(trivia) self._raw = raw @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 6 @property - def value(self): # type: () -> date + def value(self) -> date: return self - def as_string(self): # type: () -> str + 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(Date, self).__add__(other) + result = super().__add__(other) return self._new(result) @@ -707,7 +842,7 @@ class Date(Item, date): if PY38: result = date(self.year, self.month, self.day).__sub__(other) else: - result = super(Date, self).__sub__(other) + result = super().__sub__(other) if isinstance(result, date): result = self._new(result) @@ -729,29 +864,42 @@ class Time(Item, time): """ def __new__( - cls, hour, minute, second, microsecond, tzinfo, *_ - ): # type: (int, int, int, int, Optional[datetime.tzinfo], Any) -> time + cls, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + *_: Any, + ) -> time: return time.__new__(cls, hour, minute, second, microsecond, tzinfo) def __init__( - self, hour, minute, second, microsecond, tzinfo, trivia, raw - ): # type: (int, int, int, int, Optional[datetime.tzinfo], Trivia, str) -> None - super(Time, self).__init__(trivia) + self, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + trivia: Trivia, + raw: str, + ) -> None: + super().__init__(trivia) self._raw = raw @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 7 @property - def value(self): # type: () -> time + def value(self) -> time: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple: return ( self.hour, self.minute, @@ -763,38 +911,51 @@ class Time(Item, time): ) -class Array(Item, list): +class Array(Item, _CustomList): """ An array literal """ - def __init__( - self, value, trivia, multiline=False - ): # type: (list, Trivia, bool) -> None - super(Array, self).__init__(trivia) - + def __init__(self, value: list, trivia: Trivia, multiline: bool = False) -> None: + super().__init__(trivia) + self._index_map: Dict[int, int] = {} list.__init__( self, [v.value for v in value if not isinstance(v, (Whitespace, Comment))] ) self._value = value self._multiline = multiline + self._reindex() @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 8 @property - def value(self): # type: () -> list + def value(self) -> list: return self - def multiline(self, multiline): # type: (bool) -> self + 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): # type: () -> str - if not self._multiline: + def as_string(self) -> str: + if not self._multiline or not self._value: return "[{}]".format("".join(v.as_string() for v in self._value)) s = "[\n" + self.trivia.indent + " " * 4 @@ -806,113 +967,337 @@ class Array(Item, list): return s - def append(self, _item): # type: (Any) -> None - if self._value: - self._value.append(Whitespace(", ")) - - it = item(_item) - super(Array, self).append(it.value) - - self._value.append(it) - - if not PY2: - - def clear(self): - super(Array, self).clear() - - self._value.clear() - - def __iadd__(self, other): # type: (list) -> Array - if not isinstance(other, list): - return NotImplemented - - for v in other: - self.append(v) - - return self - - def __delitem__(self, key): - super(Array, self).__delitem__(key) - - j = 0 if key >= 0 else -1 - for i, v in enumerate(self._value if key >= 0 else reversed(self._value)): - if key < 0: - i = -i - 1 - - if isinstance(v, (Comment, Whitespace)): + def _reindex(self) -> None: + self._index_map.clear() + index = 0 + for i, v in enumerate(self._value): + if isinstance(v, (Whitespace, Comment)): continue + self._index_map[index] = i + index += 1 - if j == key: - del self._value[i] + def add_line( + self, + *items: Any, + indent: str = " ", + comment: Optional[str] = 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. - if i < 0 and abs(i) > len(self._value): - i += 1 + :Example: - if i < len(self._value) - 1 and isinstance(self._value[i], Whitespace): - del self._value[i] + >>> 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, + ] + """ + values = self._value[:] + new_values = [] - break + def append_item(el: Item) -> None: + if not values: + return values.append(el) + last_el = values[-1] + if ( + isinstance(el, Whitespace) + and "," not in el.s + and isinstance(last_el, Whitespace) + and "," not in last_el.s + ): + values[-1] = Whitespace(last_el.s + el.s) + else: + values.append(el) - j += 1 if key >= 0 else -1 + if newline: + append_item(Whitespace("\n")) + if indent: + append_item(Whitespace(indent)) + for i, el in enumerate(items): + el = item(el, _parent=self) + if isinstance(el, Comment) or add_comma and isinstance(el, Whitespace): + raise ValueError(f"item type {type(el)} is not allowed") + if not isinstance(el, Whitespace): + new_values.append(el.value) + append_item(el) + if add_comma: + append_item(Whitespace(",")) + if i != len(items) - 1: + append_item(Whitespace(" ")) + if comment: + indent = " " if items else "" + append_item( + Comment(Trivia(indent=indent, comment=f"# {comment}", trail="")) + ) + # Atomic manipulation + self._value[:] = values + list.extend(self, new_values) + self._reindex() + + def clear(self) -> None: + """Clear the array.""" + list.clear(self) + + self._value.clear() + self._index_map.clear() + + def __len__(self) -> int: + return list.__len__(self) + + def __getitem__(self, key: Union[int, slice]) -> Any: + return list.__getitem__(self, key) + + def __setitem__(self, key: Union[int, slice], value: Any) -> Any: + it = item(value, _parent=self) + list.__setitem__(self, key, it.value) + if isinstance(key, slice): + raise ValueError("slice assignment is not supported") + if key < 0: + key += len(self) + self._value[self._index_map[key]] = 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.value) + if pos < 0: + pos += length + if pos < 0: + pos = 0 + + items = [it] + idx = 0 + if pos < length: + try: + idx = self._index_map[pos] + except KeyError: + raise IndexError("list index out of range") + if not isinstance(it, (Whitespace, Comment)): + items.append(Whitespace(",")) + else: + idx = len(self._value) + if idx > 0: + last_item = self._value[idx - 1] + if isinstance(last_item, Whitespace) and "," not in last_item.s: + # the item has an indent, copy that + idx -= 1 + ws = last_item.s + if isinstance(it, Whitespace) and "," not in it.s: + # merge the whitespace + self._value[idx] = Whitespace(ws + it.s) + return + else: + ws = "" + has_newline = bool(set(ws) & set(TOMLChar.NL)) + has_space = ws and ws[-1] in TOMLChar.SPACES + if not has_space: + # four spaces for multiline array and single space otherwise + ws += " " if has_newline else " " + items.insert(0, Whitespace(ws)) + self._value[idx:idx] = items + i = idx - 1 + if pos > 0: # Check if the last item ends with a comma + while i >= 0 and isinstance(self._value[i], (Whitespace, Comment)): + if isinstance(self._value[i], Whitespace) and "," in self._value[i].s: + break + i -= 1 + else: + self._value.insert(i + 1, Whitespace(",")) + + self._reindex() + + def __delitem__(self, key: Union[int, slice]): + length = len(self) + list.__delitem__(self, key) + + def get_indice_to_remove(idx: int) -> Iterable[int]: + try: + real_idx = self._index_map[idx] + except KeyError: + raise IndexError("list index out of range") + yield real_idx + for i in range(real_idx + 1, len(self._value)): + if isinstance(self._value[i], Whitespace): + yield i + else: + break + + indexes = set() + if isinstance(key, slice): + for idx in range(key.start or 0, key.stop or length, key.step or 1): + indexes.update(get_indice_to_remove(idx)) + else: + indexes.update(get_indice_to_remove(length + key if key < 0 else key)) + for i in sorted(indexes, reverse=True): + del self._value[i] + while self._value and isinstance(self._value[-1], Whitespace): + self._value.pop() + self._reindex() def __str__(self): return str( [v.value for v in self._value if not isinstance(v, (Whitespace, Comment))] ) - def __repr__(self): - return str(self) - def _getstate(self, protocol=3): return self._value, self._trivia -class Table(Item, MutableMapping, dict): - """ - A table literal. - """ +AT = TypeVar("AT", bound="AbstractTable") - def __init__( - self, - value, - trivia, - is_aot_element, - is_super_table=False, - name=None, - display_name=None, - ): # type: (tomlkit.container.Container, Trivia, bool, bool, Optional[str], Optional[str]) -> None - super(Table, self).__init__(trivia) - self.name = name - self.display_name = display_name +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 - self._is_aot_element = is_aot_element - self._is_super_table = is_super_table for k, v in self._value.body: if k is not None: dict.__setitem__(self, k.key, v) @property - def value(self): # type: () -> tomlkit.container.Container + def value(self) -> "container.Container": return self._value + @overload + def append(self: AT, key: None, value: Union[Comment, Whitespace]) -> AT: + ... + + @overload + def append(self: AT, key: Union[Key, str], value: Any) -> AT: + ... + + def append(self, key, value): + raise NotImplementedError + + @overload + def add(self: AT, value: Union[Comment, Whitespace]) -> AT: + ... + + @overload + def add(self: AT, key: Union[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: Union[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 setdefault(self, key: Union[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: Union[Key, str]) -> None: + self.remove(key) + + def __getitem__(self, key: Union[Key, str]) -> Item: + return cast(Item, self._value[key]) + + def __setitem__(self, key: Union[Key, str], value: Any) -> None: + if not isinstance(value, Item): + value = item(value) + + 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 = False, + name: Optional[str] = None, + display_name: Optional[str] = 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): # type: () -> int + def discriminant(self) -> int: return 9 - def add(self, key, item=None): # type: (Union[Key, Item, str], Any) -> Item - if item is None: - if not isinstance(key, (Comment, Whitespace)): - raise ValueError( - "Non comment/whitespace items must have an associated key" - ) + 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, + ) - key, item = None, key - - return self.append(key, item) - - def append(self, key, _item): # type: (Union[Key, str], Any) -> Table + def append(self, key, _item): """ Appends a (key, item) to the table. """ @@ -922,12 +1307,13 @@ class Table(Item, MutableMapping, dict): self._value.append(key, _item) if isinstance(key, Key): - key = key.key + key = next(iter(key)).key + _item = self._value[key] if key is not None: dict.__setitem__(self, key, _item) - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) + m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if not m: return self @@ -942,107 +1328,60 @@ class Table(Item, MutableMapping, dict): return self - def raw_append(self, key, _item): # type: (Union[Key, str], Any) -> Table + def raw_append(self, key: Union[Key, str], _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) if isinstance(key, Key): - key = key.key + key = next(iter(key)).key + _item = self._value[key] if key is not None: dict.__setitem__(self, key, _item) return self - def remove(self, key): # type: (Union[Key, str]) -> Table - self._value.remove(key) - - if isinstance(key, Key): - key = key.key - - if key is not None: - dict.__delitem__(self, key) - - return self - - def is_aot_element(self): # type: () -> bool + 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): # type: () -> bool + 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.""" return self._is_super_table - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._value.as_string() # Helpers - def indent(self, indent): # type: (int) -> Table - super(Table, self).indent(indent) + 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 = "" + indent_str = "" else: - indent = m.group(1) + indent_str = m.group(1) - for k, item in self._value.body: + for _, item in self._value.body: if not isinstance(item, Whitespace): - item.trivia.indent = indent + item.trivia.indent + item.trivia.indent = indent_str + item.trivia.indent return self - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - return self._value.get(key, default) + def invalidate_display_name(self): + self.display_name = None - def setdefault( - self, key, default=None - ): # type: (Union[Key, str], Any) -> Union[Item, Container] - super(Table, self).setdefault(key, default=default) + for child in self.values(): + if hasattr(child, "invalidate_display_name"): + child.invalidate_display_name() - return self[key] - - def __getitem__(self, key): # type: (Union[Key, str]) -> Item - return self._value[key] - - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None - fix_indent = key not in self - - if not isinstance(value, Item): - value = item(value) - - self._value[key] = value - - if key is not None: - dict.__setitem__(self, key, value) - - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m or not fix_indent: - 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) - - def __delitem__(self, key): # type: (Union[Key, str]) -> None - self.remove(key) - - def __len__(self): # type: () -> int - return len(self._value) - - def __iter__(self): # type: () -> Iterator[str] - return iter(self._value) - - def __repr__(self): # type: () -> str - return repr(self._value) - - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple: return ( self._value, self._trivia, @@ -1053,32 +1392,23 @@ class Table(Item, MutableMapping, dict): ) -class InlineTable(Item, MutableMapping, dict): +class InlineTable(AbstractTable): """ An inline table literal. """ def __init__( - self, value, trivia, new=False - ): # type: (tomlkit.container.Container, Trivia, bool) -> None - super(InlineTable, self).__init__(trivia) + self, value: "container.Container", trivia: Trivia, new: bool = False + ) -> None: + super().__init__(value, trivia) - self._value = value self._new = new - for k, v in self._value.body: - if k is not None: - dict.__setitem__(self, k.key, v) - @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 10 - @property - def value(self): # type: () -> Dict - return self._value - - def append(self, key, _item): # type: (Union[Key, str], Any) -> InlineTable + def append(self, key, _item): """ Appends a (key, item) to the table. """ @@ -1101,18 +1431,7 @@ class InlineTable(Item, MutableMapping, dict): return self - def remove(self, key): # type: (Union[Key, str]) -> InlineTable - self._value.remove(key) - - if isinstance(key, Key): - key = key.key - - if key is not None: - dict.__delitem__(self, key) - - return self - - def as_string(self): # type: () -> str + def as_string(self) -> str: buf = "{" for i, (k, v) in enumerate(self._value.body): if k is None: @@ -1144,97 +1463,50 @@ class InlineTable(Item, MutableMapping, dict): return buf - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - return self._value.get(key, default) - - def setdefault( - self, key, default=None - ): # type: (Union[Key, str], Any) -> Union[Item, Container] - super(InlineTable, self).setdefault(key, default=default) - - return self[key] - - def __contains__(self, key): # type: (Union[Key, str]) -> bool - return key in self._value - - def __getitem__(self, key): # type: (Union[Key, str]) -> Item - return self._value[key] - - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None - if not isinstance(value, Item): - value = item(value) - - self._value[key] = value - - if key is not None: - dict.__setitem__(self, key, value) - - if value.trivia.comment: + def __setitem__(self, key: Union[Key, str], value: Any) -> None: + if hasattr(value, "trivia") and value.trivia.comment: value.trivia.comment = "" + super().__setitem__(key, value) - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m: - return + def __copy__(self) -> "InlineTable": + return type(self)(self._value.copy(), self._trivia.copy(), self._new) - 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) - - def __delitem__(self, key): # type: (Union[Key, str]) -> None - self.remove(key) - - def __len__(self): # type: () -> int - return len(self._value) - - def __iter__(self): # type: () -> Iterator[str] - return iter(self._value) - - def __repr__(self): - return repr(self._value) - - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple: return (self._value, self._trivia) -class String(unicode, Item): +class String(str, Item): """ A string literal. """ def __new__(cls, t, value, original, trivia): - return super(String, cls).__new__(cls, value) + return super().__new__(cls, value) - def __init__( - self, t, _, original, trivia - ): # type: (StringType, str, original, Trivia) -> None - super(String, self).__init__(trivia) + def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None: + super().__init__(trivia) self._t = t self._original = original @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 11 @property - def value(self): # type: () -> str + def value(self) -> str: return self - def as_string(self): # type: () -> str - return "{}{}{}".format(self._t.value, decode(self._original), self._t.value) + def as_string(self) -> str: + return f"{self._t.value}{decode(self._original)}{self._t.value}" def __add__(self, other): - result = super(String, self).__add__(other) + result = super().__add__(other) return self._new(result) def __sub__(self, other): - result = super(String, self).__sub__(other) + result = super().__sub__(other) return self._new(result) @@ -1242,67 +1514,104 @@ class String(unicode, Item): return String(self._t, result, result, self._trivia) def _getstate(self, protocol=3): - return self._t, unicode(self), self._original, self._trivia + return self._t, str(self), self._original, self._trivia -class AoT(Item, list): +class AoT(Item, _CustomList): """ An array of table literal """ def __init__( - self, body, name=None, parsed=False - ): # type: (List[Table], Optional[str], bool) -> None + self, body: List[Table], name: Optional[str] = None, parsed: bool = False + ) -> None: self.name = name - self._body = [] + self._body: List[Table] = [] self._parsed = parsed - super(AoT, self).__init__(Trivia(trail="")) + super().__init__(Trivia(trail="")) for table in body: self.append(table) @property - def body(self): # type: () -> List[Table] + def body(self) -> List[Table]: return self._body @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 12 @property - def value(self): # type: () -> List[Dict[Any, Any]] + def value(self) -> List[Dict[Any, Any]]: return [v.value for v in self._body] - def append(self, table): # type: (Table) -> Table + 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: Union[slice, int], value: Any) -> None: + raise NotImplementedError + + def __delitem__(self, key: Union[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)^([^ ]*)(.*)$", table.trivia.indent) + m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) if not m: - table.trivia.indent = indent + value.trivia.indent = indent else: - table.trivia.indent = m.group(1) + indent + m.group(2) + 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) - if not self._parsed and "\n" not in table.trivia.indent and self._body: - table.trivia.indent = "\n" + table.trivia.indent + 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() - self._body.append(table) - - super(AoT, self).append(table) - - return table - - def as_string(self): # type: () -> str + def as_string(self) -> str: b = "" for table in self._body: b += table.as_string() return b - def __repr__(self): # type: () -> str - return "".format(self.value) + def __repr__(self) -> str: + return f"" def _getstate(self, protocol=3): return self._body, self.name, self._parsed @@ -1313,18 +1622,18 @@ class Null(Item): A null item. """ - def __init__(self): # type: () -> None + def __init__(self) -> None: pass @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return -1 @property - def value(self): # type: () -> None + def value(self) -> None: return None - def as_string(self): # type: () -> str + def as_string(self) -> str: return "" def _getstate(self, protocol=3): diff --git a/pipenv/vendor/tomlkit/parser.py b/pipenv/vendor/tomlkit/parser.py index b702088d..e1f64dff 100644 --- a/pipenv/vendor/tomlkit/parser.py +++ b/pipenv/vendor/tomlkit/parser.py @@ -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. diff --git a/pipenv/vendor/tomlkit/source.py b/pipenv/vendor/tomlkit/source.py index 6a6a2391..6f82d946 100644 --- a/pipenv/vendor/tomlkit/source.py +++ b/pipenv/vendor/tomlkit/source.py @@ -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: diff --git a/pipenv/vendor/tomlkit/toml_char.py b/pipenv/vendor/tomlkit/toml_char.py index 1faf0aaa..11e5385d 100644 --- a/pipenv/vendor/tomlkit/toml_char.py +++ b/pipenv/vendor/tomlkit/toml_char.py @@ -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 """ diff --git a/pipenv/vendor/tomlkit/toml_file.py b/pipenv/vendor/tomlkit/toml_file.py index 3b416664..47c9f2c2 100644 --- a/pipenv/vendor/tomlkit/toml_file.py +++ b/pipenv/vendor/tomlkit/toml_file.py @@ -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())