PK!*Ktomlkit/__init__.pyfrom .api import aot from .api import array from .api import boolean from .api import comment from .api import date from .api import datetime from .api import document from .api import dumps from .api import float_ from .api import inline_table from .api import integer from .api import item from .api import key from .api import key_value from .api import loads from .api import nl from .api import parse from .api import string from .api import table from .api import time from .api import value from .api import ws __version__ = "0.4.5" PK!tomlkit/_compat.pyimport 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__.__qualname__, self._offset, ) return "%s.%s(%r, %r)" % ( self.__class__.__module__, self.__class__.__qualname__, 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) PY2 = sys.version_info[0] == 2 PY36 = sys.version_info >= (3, 6) if PY2: unicode = unicode chr = unichr else: unicode = str chr = chr def decode(string, encodings=None): if not PY2 and not isinstance(string, bytes): return string if PY2 and isinstance(string, unicode): return string encodings = encodings or ["utf-8", "latin1", "ascii"] for encoding in encodings: try: return string.decode(encoding) except (UnicodeEncodeError, UnicodeDecodeError): pass return string.decode(encodings[0], errors="ignore") PK!+ + tomlkit/_utils.pyimport re from datetime import date from datetime import datetime from datetime import time from datetime import timedelta from ._compat import decode from ._compat import timezone RFC_3339_DATETIME = re.compile( "^" "([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date "[T ]" # Separator "([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time "((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone "$" ) RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$") RFC_3339_TIME = re.compile( "^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$" ) _utc = timezone(timedelta(), "UTC") def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] m = RFC_3339_DATETIME.match(string) if m: year = int(m.group(1)) month = int(m.group(2)) day = int(m.group(3)) hour = int(m.group(4)) minute = int(m.group(5)) second = int(m.group(6)) microsecond = 0 if m.group(7): microsecond = int(("{:<06s}".format(m.group(8)))[:6]) dt = datetime(year, month, day, hour, minute, second, microsecond) if m.group(9): # Timezone tz = m.group(9) if tz == "Z": tzinfo = _utc else: sign = m.group(11)[0] hour_offset, minute_offset = int(m.group(12)), int(m.group(13)) offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60) if sign == "-": offset = -offset tzinfo = timezone( offset, "{}{}:{}".format(sign, m.group(12), m.group(13)) ) return datetime( year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo ) else: return datetime(year, month, day, hour, minute, second, microsecond) m = RFC_3339_DATE.match(string) if m: year = int(m.group(1)) month = int(m.group(2)) day = int(m.group(3)) return date(year, month, day) m = RFC_3339_TIME.match(string) if m: hour = int(m.group(1)) minute = int(m.group(2)) second = int(m.group(3)) microsecond = 0 if m.group(4): microsecond = int(("{:<06s}".format(m.group(5)))[:6]) return time(hour, minute, second, microsecond) raise ValueError("Invalid RFC 339 string") _escaped = {"b": "\b", "t": "\t", "n": "\n", "f": "\f", "r": "\r", '"': '"', "\\": "\\"} _escapes = {v: k for k, v in _escaped.items()} def escape_string(s): s = decode(s) res = [] start = 0 def flush(): if start != i: res.append(s[start:i]) return i + 1 i = 0 while i < len(s): c = s[i] if c in '"\\\n\r\t\b\f': start = flush() res.append("\\" + _escapes[c]) elif ord(c) < 0x20: start = flush() res.append("\\u%04x" % ord(c)) i += 1 flush() return "".join(res) PK! tomlkit/api.pyimport datetime as _datetime from typing import Tuple from ._utils import parse_rfc3339 from .container import Container from .items import AoT from .items import Comment from .items import InlineTable from .items import Item as _Item from .items import Array from .items import Bool from .items import Key from .items import Date from .items import DateTime from .items import Float from .items import Table from .items import Integer from .items import Trivia from .items import Whitespace from .items import String from .items import item from .parser import Parser from .toml_document import TOMLDocument as _TOMLDocument from .items import Time def loads(string): # type: (str) -> _TOMLDocument """ Parses a string into a TOMLDocument. Alias for parse(). """ return parse(string) def dumps(data): # type: (_TOMLDocument) -> str """ Dumps a TOMLDocument into a string. """ if not isinstance(data, _TOMLDocument) and isinstance(data, dict): data = item(data) return data.as_string() def parse(string): # type: (str) -> _TOMLDocument """ Parses a string into a TOMLDocument. """ return Parser(string).parse() def document(): # type: () -> _TOMLDocument """ Returns a new TOMLDocument instance. """ return _TOMLDocument() # Items def integer(raw): # type: (str) -> Integer return item(int(raw)) def float_(raw): # type: (str) -> Float return item(float(raw)) def boolean(raw): # type: (str) -> Bool return item(raw == "true") def string(raw): # type: (str) -> String return item(raw) def date(raw): # type: (str) -> Date value = parse_rfc3339(raw) if not isinstance(value, _datetime.date): raise ValueError("date() only accepts date strings.") return item(value) def time(raw): # type: (str) -> Time value = parse_rfc3339(raw) if not isinstance(value, _datetime.time): raise ValueError("time() only accepts time strings.") return item(value) def datetime(raw): # type: (str) -> DateTime value = parse_rfc3339(raw) if not isinstance(value, _datetime.datetime): raise ValueError("datetime() only accepts datetime strings.") return item(value) def array(raw=None): # type: (str) -> Array if raw is None: raw = "[]" return value(raw) def table(): # type: () -> Table return Table(Container(), Trivia(), False) def inline_table(): # type: () -> InlineTable return InlineTable(Container(), Trivia()) def aot(): # type: () -> AoT return AoT([]) def key(k): # type: (str) -> Key return Key(k) def value(raw): # type: (str) -> _Item return Parser(raw)._parse_value() def key_value(src): # type: (str) -> Tuple[Key, _Item] return Parser(src)._parse_key_value() def ws(src): # type: (str) -> Whitespace return Whitespace(src, fixed=True) def nl(): # type: () -> Whitespace return ws("\n") def comment(string): # type: (str) -> Comment return Comment(Trivia(comment_ws=" ", comment="# " + string)) PK!sAAtomlkit/container.pyfrom __future__ import unicode_literals from typing import Any from typing import Dict from typing import Generator from typing import List from typing import Optional from typing import Tuple from typing import Union from ._compat import decode from .exceptions import KeyAlreadyPresent from .exceptions import NonExistentKey from .items import AoT from .items import Comment from .items import Item from .items import Key from .items import Null from .items import Table from .items import Whitespace from .items import item as _item class Container(dict): """ A container for items within a TOMLDocument. """ def __init__(self, parsed=False): # type: (bool) -> None self._map = {} # type: Dict[Key, int] self._body = [] # type: List[Tuple[Optional[Key], Item]] self._parsed = parsed @property def body(self): # type: () -> List[Tuple[Optional[Key], Item]] return self._body @property def value(self): # type: () -> Dict[Any, Any] d = {} for k, v in self._body: if k is None: continue k = k.key v = v.value if isinstance(v, Container): v = v.value if k in d: d[k].update(v) else: d[k] = v return d def parsing(self, parsing): # type: (bool) -> None self._parsed = parsing for k, v in self._body: if isinstance(v, Table): v.value.parsing(parsing) elif isinstance(v, AoT): for t in v.body: t.value.parsing(parsing) def add( self, key, item=None ): # type: (Union[Key, Item, str], Optional[Item]) -> Container """ Adds an item to the current Container. """ if item is None: if not isinstance(key, (Comment, Whitespace)): raise ValueError( "Non comment/whitespace items must have an associated key" ) key, item = None, key return self.append(key, item) def append(self, key, item): # type: (Union[Key, str], Item) -> Container if not isinstance(key, Key) and key is not None: key = Key(key) if not isinstance(item, Item): item = _item(item) 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" if isinstance(item, AoT) and self._body and not self._parsed: if item and "\n" not in item[0].trivia.indent: item[0].trivia.indent = "\n" + item[0].trivia.indent else: self.append(None, Whitespace("\n")) if key is not None and key in self: current = self._body[self._map[key]][1] if isinstance(item, Table): if not isinstance(current, (Table, AoT)): raise KeyAlreadyPresent(key) if item.is_aot_element(): # New AoT element found later on # Adding it to the current AoT if not isinstance(current, AoT): current = AoT([current, item], parsed=self._parsed) self._replace(key, key, current) else: current.append(item) return self elif current.is_super_table(): if item.is_super_table(): for k, v in item.value.body: current.append(k, v) return self else: raise KeyAlreadyPresent(key) elif isinstance(item, AoT): if not isinstance(current, AoT): raise KeyAlreadyPresent(key) for table in item.body: current.append(table) return self else: raise KeyAlreadyPresent(key) is_table = isinstance(item, (Table, AoT)) if key is not None and self._body and not self._parsed: # If there is already at least one table in the current container # and the given item is not a table, we need to find the last # 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: if isinstance(v, Null): # This happens only after deletion continue if isinstance(v, Whitespace) and not v.is_fixed(): continue if not is_table and isinstance(v, (Table, AoT)): break key_after = k or idx idx += 1 if key_after is not None: if isinstance(key_after, int): if key_after + 1 < len(self._body) - 1: 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 ): previous_item.trivia.trail += "\n" else: return self._insert_after(key_after, key, item) else: return self._insert_at(0, key, item) self._map[key] = len(self._body) self._body.append((key, item)) if key is not None: super(Container, self).__setitem__(key.key, item.value) return self def remove(self, key): # type: (Union[Key, str]) -> Container if not isinstance(key, Key): key = Key(key) idx = self._map.pop(key, None) if idx is None: raise NonExistentKey(key) self._body[idx] = (None, Null()) super(Container, self).__delitem__(key.key) return self def _insert_after( self, key, other_key, item ): # type: (Union[str, Key], Union[str, Key], Union[Item, Any]) -> Container if key is None: raise ValueError("Key cannot be null in insert_after()") if key not in self: raise NonExistentKey(key) if not isinstance(key, Key): key = Key(key) if not isinstance(other_key, Key): other_key = Key(other_key) item = _item(item) idx = self._map[key] current_item = self._body[idx][1] if "\n" not in current_item.trivia.trail: current_item.trivia.trail += "\n" # Increment indices after the current index for k, v in self._map.items(): if v > idx: self._map[k] = v + 1 self._map[other_key] = idx + 1 self._body.insert(idx + 1, (other_key, item)) if key is not None: super(Container, self).__setitem__(other_key.key, item.value) return self def _insert_at( self, idx, key, item ): # type: (int, Union[str, Key], Union[Item, Any]) -> Container if idx > len(self._body) - 1: raise ValueError("Unable to insert at position {}".format(idx)) if not isinstance(key, Key): key = Key(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 ): previous_item.trivia.trail += "\n" # Increment indices after the current index for k, v in self._map.items(): if v >= idx: self._map[k] = v + 1 self._map[key] = idx self._body.insert(idx, (key, item)) if key is not None: super(Container, self).__setitem__(key.key, item.value) return self def item(self, key): # type: (Union[Key, str]) -> Item if not isinstance(key, Key): key = Key(key) idx = self._map.get(key, None) if idx is None: raise NonExistentKey(key) return self._body[idx][1] def last_item(self): # type: () -> Optional[Item] if self._body: return self._body[-1][1] def as_string(self, prefix=None): # type: () -> str s = "" for k, v in self._body: if k is not None: if False: key = k.as_string() for _k, _v in v.value.body: if _k is None: s += v.as_string() elif isinstance(_v, Table): s += v.as_string(prefix=key) else: _key = key if prefix is not None: _key = prefix + "." + _key s += "{}{}{}{}{}{}{}".format( _v.trivia.indent, _key + "." + decode(_k.as_string()), _k.sep, decode(_v.as_string()), _v.trivia.comment_ws, decode(_v.trivia.comment), _v.trivia.trail, ) elif isinstance(v, Table): s += self._render_table(k, v) elif isinstance(v, AoT): s += self._render_aot(k, v) else: s += self._render_simple_item(k, v) else: s += self._render_simple_item(k, v) return s def _render_table( self, key, table, prefix=None ): # (Key, Table, Optional[str]) -> str cur = "" if table.display_name is not None: _key = table.display_name else: _key = key.as_string() if prefix is not None: _key = prefix + "." + _key if not table.is_super_table(): open_, close = "[", "]" if table.is_aot_element(): open_, close = "[[", "]]" cur += "{}{}{}{}{}{}{}{}".format( table.trivia.indent, open_, decode(_key), close, table.trivia.comment_ws, decode(table.trivia.comment), table.trivia.trail, "\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else "", ) for k, v in table.value.body: if isinstance(v, Table): if v.is_super_table(): if k.is_dotted() and not key.is_dotted(): # Dotted key inside table cur += self._render_table(k, v) else: cur += self._render_table(k, v, prefix=_key) else: cur += self._render_table(k, v, prefix=_key) elif isinstance(v, AoT): cur += self._render_aot(k, v, prefix=_key) else: cur += self._render_simple_item( k, v, prefix=_key if key.is_dotted() else None ) return cur def _render_aot(self, key, aot, prefix=None): _key = key.as_string() if prefix is not None: _key = prefix + "." + _key cur = "" _key = decode(_key) for table in aot.body: cur += self._render_aot_table(table, prefix=_key) return cur def _render_aot_table(self, table, prefix=None): # (Table, Optional[str]) -> str cur = "" _key = prefix or "" if not table.is_super_table(): open_, close = "[[", "]]" cur += "{}{}{}{}{}{}{}".format( table.trivia.indent, open_, decode(_key), close, table.trivia.comment_ws, decode(table.trivia.comment), table.trivia.trail, ) for k, v in table.value.body: if isinstance(v, Table): if v.is_super_table(): if k.is_dotted(): # Dotted key inside table cur += self._render_table(k, v) else: cur += self._render_table(k, v, prefix=_key) else: cur += self._render_table(k, v, prefix=_key) elif isinstance(v, AoT): cur += self._render_aot(k, v, prefix=_key) else: cur += self._render_simple_item(k, v) return cur def _render_simple_item(self, key, item, prefix=None): if key is None: return item.as_string() _key = key.as_string() if prefix is not None: _key = prefix + "." + _key return "{}{}{}{}{}{}{}".format( item.trivia.indent, decode(_key), key.sep, decode(item.as_string()), item.trivia.comment_ws, decode(item.trivia.comment), item.trivia.trail, ) # Dictionary methods def keys(self): # type: () -> Generator[str] for k, _ in self._body: if k is None: continue yield k.key def values(self): # type: () -> Generator[Item] for k, v in self._body: if k is None: continue yield v.value def items(self): # type: () -> Generator[Item] for k, v in self.value.items(): if k is None: continue yield k, v def update(self, other): # type: (Dict) -> None for k, v in other.items(): self[k] = v def __contains__(self, key): # type: (Union[Key, str]) -> bool if not isinstance(key, Key): key = Key(key) return key in self._map def __getitem__(self, key): # type: (Union[Key, str]) -> Item if not isinstance(key, Key): key = Key(key) idx = self._map.get(key, None) if idx is None: raise NonExistentKey(key) item = self._body[idx][1] return item.value 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 __delitem__(self, key): # type: (Union[Key, str]) -> None self.remove(key) def _replace( self, key, new_key, value ): # type: (Union[Key, str], Union[Key, str], Item) -> None if not isinstance(key, Key): key = Key(key) if not isinstance(new_key, Key): new_key = Key(new_key) idx = self._map.get(key, None) if idx is None: raise NonExistentKey(key) self._replace_at(idx, new_key, value) def _replace_at( self, idx, new_key, value ): # type: (int, Union[Key, str], Item) -> None k, v = self._body[idx] self._map[new_key] = self._map.pop(k) 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 self._body[idx] = (new_key, value) super(Container, self).__setitem__(new_key.key, value.value) def __str__(self): # type: () -> str return str(self.value) def __eq__(self, other): # type: (Dict) -> bool if not isinstance(other, dict): return NotImplemented return self.value == other def _getstate(self, protocol): return (self._parsed,) def __reduce__(self): return self.__reduce_ex__(2) def __reduce_ex__(self, protocol): return ( self.__class__, self._getstate(protocol), (self._map, self._body, self._parsed), ) def __setstate__(self, state): self._map = state[0] self._body = state[1] self._parsed = state[2] PK!5PPtomlkit/exceptions.pyfrom typing import Optional class TOMLKitError(Exception): pass class ParseError(ValueError, TOMLKitError): """ This error occurs when the parser encounters a syntax error in the TOML being parsed. The error references the line and location within the line where the error was encountered. """ def __init__( self, line, col, message=None ): # type: (int, int, Optional[str]) -> 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) ) class MixedArrayTypesError(ParseError): """ An array was found that had two or more element types. """ def __init__(self, line, col): # type: (int, int) -> None message = "Mixed types found in array" super(MixedArrayTypesError, self).__init__(line, col, message=message) class InvalidNumberOrDateError(ParseError): """ A numeric or date field was improperly specified. """ def __init__(self, line, col): # type: (int, int) -> None message = "Invalid number or date format" super(InvalidNumberOrDateError, self).__init__(line, col, message=message) 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)) super(UnexpectedCharError, self).__init__(line, col, message=message) class EmptyKeyError(ParseError): """ An empty key was found during parsing. """ def __init__(self, line, col): # type: (int, int) -> None message = "Empty key" super(EmptyKeyError, self).__init__(line, col, message=message) class EmptyTableNameError(ParseError): """ An empty table name was found during parsing. """ def __init__(self, line, col): # type: (int, int) -> None message = "Empty table name" super(EmptyTableNameError, self).__init__(line, col, message=message) 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(char) super(InvalidCharInStringError, self).__init__(line, col, message=message) class UnexpectedEofError(ParseError): """ The TOML being parsed ended before the end of a statement. """ def __init__(self, line, col): # type: (int, int) -> None message = "Unexpected end of file" super(UnexpectedEofError, self).__init__(line, col, message=message) class InternalParserError(ParseError): """ An error that indicates a bug in the parser. """ def __init__(self, line, col, message=None): # type: (int, int) -> None msg = "Internal parser error" if message: msg += " ({})".format(message) super(InternalParserError, self).__init__(line, col, message=msg) class NonExistentKey(KeyError, TOMLKitError): """ A non-existent key was used. """ def __init__(self, key): message = 'Key "{}" does not exist.'.format(key) super(NonExistentKey, self).__init__(message) class KeyAlreadyPresent(TOMLKitError): """ An already present key was used. """ def __init__(self, key): message = 'Key "{}" already exists.'.format(key) super(KeyAlreadyPresent, self).__init__(message) PK!v]M n ntomlkit/items.pyfrom __future__ import unicode_literals import re import string from datetime import date from datetime import datetime from datetime import time from enum import Enum from typing import Any from typing import Dict from typing import Generator from typing import List from typing import Optional from typing import Union from ._compat import PY2 from ._compat import decode from ._compat import unicode from ._utils import escape_string def item(value, _parent=None): from .container import Container if isinstance(value, Item): return value if isinstance(value, bool): return Bool(value, Trivia()) elif isinstance(value, int): return Integer(value, Trivia(), str(value)) elif isinstance(value, float): return Float(value, Trivia(), str(value)) elif isinstance(value, dict): val = Table(Container(), Trivia(), False) for k, v in sorted(value.items(), key=lambda i: (isinstance(i[1], dict), i[0])): val[k] = item(v, _parent=val) return val elif isinstance(value, list): if value and isinstance(value[0], dict): a = AoT([]) else: a = Array([], Trivia()) for v in value: if isinstance(v, dict): table = Table(Container(), Trivia(), True) for k, _v in sorted( v.items(), key=lambda i: (isinstance(i[1], dict), i[0]) ): i = item(_v) if isinstance(table, InlineTable): i.trivia.trail = "" table[k] = item(i) v = table a.append(v) return a elif isinstance(value, (str, unicode)): escaped = escape_string(value) return String(StringType.SLB, value, escaped, Trivia()) elif isinstance(value, datetime): return DateTime(value, Trivia(), value.isoformat().replace("+00:00", "Z")) elif isinstance(value, date): return Date(value, Trivia(), value.isoformat()) elif isinstance(value, time): return Time(value, Trivia(), value.isoformat()) raise ValueError("Invalid type {}".format(type(value))) class StringType(Enum): SLB = '"' MLB = '"""' SLL = "'" MLL = "'''" def is_literal(self): # type: () -> bool return self in {StringType.SLL, StringType.MLL} def is_multiline(self): # type: () -> bool return self in {StringType.MLB, StringType.MLL} class Trivia: """ Trivia information (aka metadata). """ def __init__( self, indent=None, comment_ws=None, comment=None, trail=None ): # type: (str, str, str, str) -> None # Whitespace before a value. self.indent = indent or "" # Whitespace after a value, but before a comment. self.comment_ws = comment_ws or "" # Comment, starting with # character, or empty string if no comment. self.comment = comment or "" # Trailing newline. if trail is None: trail = "\n" self.trail = trail class KeyType(Enum): """ The type of a Key. Keys can be bare (unquoted), or quoted using basic ("), or literal (') quotes following the same escaping rules as single-line StringType. """ Bare = "" Basic = '"' Literal = "'" class Key: """ A key value. """ def __init__(self, k, t=None, sep=None, dotted=False): # type: (str) -> None if t is None: if any( [c not in string.ascii_letters + string.digits + "-" + "_" for c in k] ): t = KeyType.Basic else: t = KeyType.Bare self.t = t if sep is None: sep = " = " self.sep = sep self.key = k self._dotted = dotted @property def delimiter(self): # type: () -> str return self.t.value def is_dotted(self): # type: () -> bool return self._dotted def as_string(self): # type: () -> str return "{}{}{}".format(self.delimiter, self.key, self.delimiter) def __hash__(self): # type: () -> int return hash(self.key) def __eq__(self, other): # type: (Key) -> bool if isinstance(other, Key): return 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 Item(object): """ An item within a TOML document. """ def __init__(self, trivia): # type: (Trivia) -> None self._trivia = trivia @property def trivia(self): # type: () -> Trivia return self._trivia @property def discriminant(self): # type: () -> int raise NotImplementedError() def as_string(self): # type: () -> str raise NotImplementedError() # Helpers def comment(self, comment): # type: (str) -> Item if not comment.strip().startswith("#"): comment = "# " + comment self._trivia.comment_ws = " " self._trivia.comment = comment return self def indent(self, indent): # type: (int) -> Item if self._trivia.indent.startswith("\n"): self._trivia.indent = "\n" + " " * indent else: self._trivia.indent = " " * indent return self def _getstate(self, protocol=3): return (self._trivia,) def __reduce__(self): return self.__reduce_ex__(2) def __reduce_ex__(self, protocol): return self.__class__, self._getstate(protocol) class Whitespace(Item): """ A whitespace literal. """ def __init__(self, s, fixed=False): # type: (str, bool) -> None self._s = s self._fixed = fixed @property def s(self): # type: () -> str return self._s @property def value(self): # type: () -> str return self._s @property def trivia(self): # type: () -> Trivia raise RuntimeError("Called trivia on a Whitespace variant.") @property def discriminant(self): # type: () -> int return 0 def is_fixed(self): # type: () -> bool return self._fixed def as_string(self): # type: () -> str return self._s def __repr__(self): # type: () -> str return "<{} {}>".format(self.__class__.__name__, repr(self._s)) def _getstate(self, protocol=3): return self._s, self._fixed class Comment(Item): """ A comment literal. """ @property def discriminant(self): # type: () -> int return 1 def as_string(self): # type: () -> 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)) 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 __init__(self, _, trivia, raw): # type: (int, Trivia, str) -> None super(Integer, self).__init__(trivia) self._raw = raw self._sign = False if re.match(r"^[+\-]\d+$", raw): self._sign = True @property def discriminant(self): # type: () -> int return 2 @property def value(self): # type: () -> int return self def as_string(self): # type: () -> str return self._raw def __add__(self, other): result = super(Integer, self).__add__(other) return self._new(result) def __radd__(self, other): result = super(Integer, self).__radd__(other) if isinstance(other, Integer): return self._new(result) return result def __sub__(self, other): result = super(Integer, self).__sub__(other) return self._new(result) def __rsub__(self, other): result = super(Integer, self).__rsub__(other) if isinstance(other, Integer): return self._new(result) return result def _new(self, result): raw = str(result) if self._sign: sign = "+" if result >= 0 else "-" raw = sign + raw return Integer(result, self._trivia, raw) def _getstate(self, protocol=3): return int(self), self._trivia, self._raw 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 __init__(self, _, trivia, raw): # type: (float, Trivia, str) -> None super(Float, self).__init__(trivia) self._raw = raw self._sign = False if re.match(r"^[+\-].+$", raw): self._sign = True @property def discriminant(self): # type: () -> int return 3 @property def value(self): # type: () -> float return self def as_string(self): # type: () -> str return self._raw def __add__(self, other): result = super(Float, self).__add__(other) return self._new(result) def __radd__(self, other): result = super(Float, self).__radd__(other) if isinstance(other, Float): return self._new(result) return result def __sub__(self, other): result = super(Float, self).__sub__(other) return self._new(result) def __rsub__(self, other): result = super(Float, self).__rsub__(other) if isinstance(other, Float): return self._new(result) return result def _new(self, result): raw = str(result) if self._sign: sign = "+" if result >= 0 else "-" raw = sign + raw return Float(result, self._trivia, raw) def _getstate(self, protocol=3): return float(self), self._trivia, self._raw class Bool(Item): """ A boolean literal. """ def __init__(self, value, trivia): # type: (float, Trivia) -> None super(Bool, self).__init__(trivia) self._value = value @property def discriminant(self): # type: () -> int return 4 @property def value(self): # type: () -> bool return self._value def as_string(self): # type: () -> str return str(self._value).lower() def _getstate(self, protocol=3): return self._value, self._trivia class DateTime(Item, datetime): """ A datetime literal. """ def __new__(cls, value, *_): # type: (..., datetime, ...) -> datetime return datetime.__new__( cls, value.year, value.month, value.day, value.hour, value.minute, value.second, value.microsecond, tzinfo=value.tzinfo, ) def __init__(self, _, trivia, raw): # type: (datetime, Trivia, str) -> None super(DateTime, self).__init__(trivia) self._raw = raw @property def discriminant(self): # type: () -> int return 5 @property def value(self): # type: () -> datetime return self def as_string(self): # type: () -> str return self._raw def __add__(self, other): result = super(DateTime, self).__add__(other) return self._new(result) def __sub__(self, other): result = super(DateTime, self).__sub__(other) return self._new(result) def _new(self, result): raw = result.isoformat() return DateTime(result, self._trivia, raw) def _getstate(self, protocol=3): return ( datetime( self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond, self.tzinfo, ), self._trivia, self._raw, ) class Date(Item, date): """ A date literal. """ def __new__(cls, value, *_): # type: (..., date, ...) -> date return date.__new__(cls, value.year, value.month, value.day) def __init__(self, _, trivia, raw): # type: (date, Trivia, str) -> None super(Date, self).__init__(trivia) self._raw = raw @property def discriminant(self): # type: () -> int return 6 @property def value(self): # type: () -> date return self def as_string(self): # type: () -> str return self._raw def __add__(self, other): result = super(Date, self).__add__(other) return self._new(result) def __sub__(self, other): result = super(Date, self).__sub__(other) return self._new(result) def _new(self, result): raw = result.isoformat() return Date(result, self._trivia, raw) def _getstate(self, protocol=3): return (datetime(self.year, self.month, self.day), self._trivia, self._raw) class Time(Item, time): """ A time literal. """ def __new__(cls, value, *_): # type: (time, ...) -> time return time.__new__( cls, value.hour, value.minute, value.second, value.microsecond ) def __init__(self, _, trivia, raw): # type: (time, Trivia, str) -> None super(Time, self).__init__(trivia) self._raw = raw @property def discriminant(self): # type: () -> int return 7 @property def value(self): # type: () -> time return self def as_string(self): # type: () -> str return self._raw def _getstate(self, protocol=3): return ( time(self.hour, self.minute, self.second, self.microsecond, self.tzinfo), self._trivia, self._raw, ) class Array(Item, list): """ An array literal """ def __init__(self, value, trivia): # type: (list, Trivia) -> None super(Array, self).__init__(trivia) list.__init__( self, [v.value for v in value if not isinstance(v, (Whitespace, Comment))] ) self._value = value @property def discriminant(self): # type: () -> int return 8 @property def value(self): # type: () -> list return self def is_homogeneous(self): # type: () -> bool if not self: return True discriminants = [ i.discriminant for i in self._value if not isinstance(i, (Whitespace, Comment)) ] return len(set(discriminants)) == 1 def as_string(self): # type: () -> str return "[{}]".format("".join(v.as_string() for v in self._value)) def append(self, _item): # type: () -> None if self._value: self._value.append(Whitespace(", ")) it = item(_item) super(Array, self).append(it.value) self._value.append(it) if not self.is_homogeneous(): raise ValueError("Array has mixed types elements") 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)): continue if j == key: del self._value[i] if i < 0 and abs(i) > len(self._value): i += 1 if i < len(self._value) - 1 and isinstance(self._value[i], Whitespace): del self._value[i] break j += 1 if key >= 0 else -1 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, dict): """ A table literal. """ def __init__( self, value, trivia, is_aot_element, is_super_table=False, name=None, display_name=None, ): # type: (tomlkit.container.Container, Trivia, bool, ...) -> None super(Table, self).__init__(trivia) self.name = name self.display_name = display_name 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: super(Table, self).__setitem__(k.key, v) @property def value(self): # type: () -> tomlkit.container.Container return self._value @property def discriminant(self): # type: () -> int return 9 @property def value(self): # type: () -> tomlkit.container.Container return self._value 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" ) key, item = None, key return self.append(key, item) def append(self, key, _item): # type: (Union[Key, str], Any) -> Table """ Appends a (key, item) to the table. """ if not isinstance(_item, Item): _item = item(_item) self._value.append(key, _item) if isinstance(key, Key): key = key.key if key is not None: super(Table, self).__setitem__(key, _item) m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if not m: return self indent = m.group(1) if not isinstance(_item, Whitespace): m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent) if not m: _item.trivia.indent = indent else: _item.trivia.indent = m.group(1) + indent + m.group(2) return self def remove(self, key): # type: (Union[Key, str]) -> Table self._value.remove(key) if isinstance(key, Key): key = key.key if key is not None: super(Table, self).__delitem__(key) return self def is_aot_element(self): # type: () -> bool return self._is_aot_element def is_super_table(self): # type: () -> bool return self._is_super_table def as_string(self, prefix=None): # type: () -> str return self._value.as_string(prefix=prefix) # Helpers def indent(self, indent): # type: (int) -> Table super(Table, self).indent(indent) m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if not m: indent = "" else: indent = m.group(1) for k, item in self._value.body: if not isinstance(item, Whitespace): item.trivia.indent = indent + item.trivia.indent return self def keys(self): # type: () -> Generator[str] for k in self._value.keys(): yield k def values(self): # type: () -> Generator[Item] for v in self._value.values(): yield v def items(self): # type: () -> Generator[Item] for k, v in self._value.items(): yield k, v def update(self, other): # type: (Dict) -> None for k, v in other.items(): self[k] = v 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: super(Table, self).__setitem__(key, value) 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) def __delitem__(self, key): # type: (Union[Key, str]) -> None self.remove(key) def __repr__(self): return super(Table, self).__repr__() def _getstate(self, protocol=3): return ( self._value, self._trivia, self._is_aot_element, self._is_super_table, self.name, self.display_name, ) class InlineTable(Item, dict): """ An inline table literal. """ def __init__( self, value, trivia ): # type: (tomlkit.container.Container, Trivia) -> None super(InlineTable, self).__init__(trivia) self._value = value for k, v in self._value.body: if k is not None: super(InlineTable, self).__setitem__(k.key, v) @property def discriminant(self): # type: () -> int return 10 @property def value(self): # type: () -> Dict return self._value def append(self, key, _item): # type: (Union[Key, str], Any) -> InlineTable """ Appends a (key, item) to the table. """ if not isinstance(_item, Item): _item = item(_item) if not isinstance(_item, (Whitespace, Comment)): if not _item.trivia.indent and len(self._value) > 0: _item.trivia.indent = " " self._value.append(key, _item) if isinstance(key, Key): key = key.key if key is not None: super(InlineTable, self).__setitem__(key, _item) 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: super(InlineTable, self).__delitem__(key) return self def as_string(self): # type: () -> str buf = "{" for i, (k, v) in enumerate(self._value.body): if k is None: if i == len(self._value.body) - 1: buf = buf.rstrip(",") buf += v.as_string() continue buf += "{}{}{}{}{}{}".format( v.trivia.indent, k.as_string(), k.sep, v.as_string(), v.trivia.comment, v.trivia.trail.replace("\n", ""), ) if i != len(self._value.body) - 1: buf += "," buf += "}" return buf def keys(self): # type: () -> Generator[str] for k in self._value.keys(): yield k def values(self): # type: () -> Generator[Item] for v in self._value.values(): yield v def items(self): # type: () -> Generator[Item] for k, v in self._value.items(): yield k, v def update(self, other): # type: (Dict) -> None for k, v in other.items(): self[k] = v 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: super(InlineTable, self).__setitem__(key, value) 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) def __delitem__(self, key): # type: (Union[Key, str]) -> None self.remove(key) def __repr__(self): return super(InlineTable, self).__repr__() def _getstate(self, protocol=3): return (self._value, self._trivia) class String(unicode, Item): """ A string literal. """ def __new__(cls, t, value, original, trivia): return super(String, cls).__new__(cls, value) def __init__( self, t, _, original, trivia ): # type: (StringType, str, original, Trivia) -> None super(String, self).__init__(trivia) self._t = t self._original = original @property def discriminant(self): # type: () -> int return 11 @property def value(self): # type: () -> str return self def as_string(self): # type: () -> str return "{}{}{}".format(self._t.value, decode(self._original), self._t.value) def __add__(self, other): result = super(String, self).__add__(other) return self._new(result) def __sub__(self, other): result = super(String, self).__sub__(other) return self._new(result) def _new(self, result): return String(self._t, result, result, self._trivia) def _getstate(self, protocol=3): return self._t, unicode(self), self._original, self._trivia class AoT(Item, list): """ An array of table literal """ def __init__( self, body, name=None, parsed=False ): # type: (List[Table], Optional[str]) -> None self.name = name self._body = [] self._parsed = parsed super(AoT, self).__init__(Trivia(trail="")) for table in body: self.append(table) @property def body(self): # type: () -> List[Table] return self._body @property def discriminant(self): # type: () -> int return 12 @property def value(self): # type: () -> List[Dict[Any, Any]] return [v.value for v in self._body] def append(self, table): # type: (Table) -> Table m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if m: indent = m.group(1) m = re.match("(?s)^([^ ]*)(.*)$", table.trivia.indent) if not m: table.trivia.indent = indent else: table.trivia.indent = m.group(1) + indent + m.group(2) if not self._parsed and "\n" not in table.trivia.indent and self._body: table.trivia.indent = "\n" + table.trivia.indent self._body.append(table) super(AoT, self).append(table) return table def as_string(self): # type: () -> str b = "" for table in self._body: b += table.as_string(prefix=self.name) return b def __repr__(self): # type: () -> str return "".format(self.value) def _getstate(self, protocol=3): return self._body, self.name, self._parsed class Null(Item): """ A null item. """ def __init__(self): # type: () -> None pass @property def discriminant(self): # type: () -> int return -1 @property def value(self): # type: () -> None return None def as_string(self): # type: () -> str return "" def _getstate(self, protocol=3): return tuple() PK!徕~~tomlkit/parser.py# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime import itertools import re import string from copy import copy from typing import Iterator from typing import Optional from typing import Tuple from typing import Union from ._compat import PY2 from ._compat import chr from ._compat import decode from ._utils import _escaped from ._utils import parse_rfc3339 from .container import Container from .exceptions import EmptyKeyError from .exceptions import EmptyTableNameError from .exceptions import InternalParserError from .exceptions import InvalidCharInStringError from .exceptions import InvalidNumberOrDateError from .exceptions import MixedArrayTypesError from .exceptions import ParseError from .exceptions import UnexpectedCharError from .exceptions import UnexpectedEofError 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 Float from .items import InlineTable from .items import Integer from .items import Key from .items import KeyType from .items import Null from .items import String from .items import StringType from .items import Table from .items import Time from .items import Trivia from .items import Whitespace from .toml_char import TOMLChar from .toml_document import TOMLDocument class Parser: """ Parser for TOML documents. """ def __init__(self, string): # type: (str) -> None # Input to parse self._src = decode(string) # type: str # Iterator used for getting characters from src. self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self._src)]) # Current byte offset into src. self._idx = 0 # Current character self._current = TOMLChar("") # type: TOMLChar # Index into src between which and idx slices will be extracted self._marker = 0 self._aot_stack = [] self.inc() def extract(self): # type: () -> str """ Extracts the value between marker and index """ if self.end(): return self._src[self._marker :] else: return self._src[self._marker : self._idx] def inc(self): # type: () -> bool """ Increments the parser if the end of the input has not been reached. Returns whether or not it was able to advance. """ try: self._idx, self._current = next(self._chars) return True except StopIteration: self._idx = len(self._src) self._current = TOMLChar("\0") return False def inc_n(self, n): # type: (int) -> bool """ Increments the parser by n characters if the end of the input has not been reached. """ for _ in range(n): if not self.inc(): return False return True def end(self): # type: () -> bool """ Returns True if the parser has reached the end of the input. """ return self._idx >= len(self._src) or self._current == "\0" def mark(self): # type: () -> None """ Sets the marker to the index's current position """ self._marker = self._idx def parse(self): # type: () -> TOMLDocument body = TOMLDocument(True) # Take all keyvals outside of tables/AoT's. while not self.end(): # Break out if a table is found if self._current == "[": break # Otherwise, take and append one KV item = self._parse_item() if not item: break key, value = item if key is not None and key.is_dotted(): # 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() while not self.end(): key, value = self._parse_table() 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) body.append(key, value) body.parsing(False) return body def _merge_ws(self, item, container): # type: (Item, Container) -> bool """ Merges the given Item with the last one currently in the given Container if both are whitespace items. Returns True if the items were merged. """ last = container.last_item() if not last: return False if not isinstance(item, Whitespace) or not isinstance(last, Whitespace): return False start = self._idx - (len(last.s) + len(item.s)) container.body[-1] = ( container.body[-1][0], Whitespace(self._src[start : self._idx]), ) return True def parse_error(self, kind=ParseError, args=None): # type: () -> None """ Creates a generic "parse error" at the current position. """ line, col = self._to_linecol(self._idx) if args: return kind(line, col, *args) else: return kind(line, col) def _to_linecol(self, offset): # type: (int) -> Tuple[int, int] cur = 0 for i, line in enumerate(self._src.splitlines()): if cur + len(line) + 1 > offset: return (i + 1, offset - cur) cur += len(line) + 1 return len(self._src.splitlines()), 0 def _is_child(self, parent, child): # type: (str, str) -> 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)) 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 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, t=t, sep="") current = "" t = KeyType.Bare continue elif c in {"'", '"'}: if in_name: if t == KeyType.Literal and c == '"': current += c continue if c != t.value: raise self.parse_error() in_name = False else: in_name = True t = KeyType.Literal if c == "'" else KeyType.Basic continue elif in_name or c.is_bare_key_char(): current += c else: raise self.parse_error() if current: yield Key(current, t=t, sep="") def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]] """ Attempts to parse the next item and returns it, along with its key if the item is value-like. """ self.mark() saved_idx = self._save_idx() while True: c = self._current if c == "\n": # Found a newline; Return all whitespace found up to this point. self.inc() return (None, Whitespace(self.extract())) elif c in " \t\r": # Skip whitespace. if not self.inc(): return (None, Whitespace(self.extract())) elif c == "#": # Found a comment, parse it indent = self.extract() cws, comment, trail = self._parse_comment_trail() return (None, Comment(Trivia(indent, cws, comment, trail))) elif c == "[": # Found a table, delegate to the calling function. return else: # Begining of a KV pair. # Return to beginning of whitespace so it gets included # as indentation for the KV about to be parsed. self._restore_idx(*saved_idx) key, value = self._parse_key_value(True) return key, value def _save_idx(self): # type: () -> Tuple[Iterator, int, str] if PY2: # Python 2.7 does not allow to directly copy # an iterator, so we have to make tees of the original # chars iterator. chars1, chars2 = itertools.tee(self._chars) # We can no longer use the original chars iterator. self._chars = chars1 return chars2, self._idx, self._current return copy(self._chars), self._idx, self._current def _restore_idx(self, chars, idx, current): # type: (Iterator, int, str) -> None self._chars = chars self._idx = idx self._current = current def _parse_comment_trail(self): # type: () -> Tuple[str, str, str] """ Returns (comment_ws, comment, trail) If there is no comment, comment_ws and comment will simply be empty. """ if self.end(): return "", "", "" comment = "" comment_ws = "" self.mark() while True: c = self._current if c == "\n": break elif c == "#": comment_ws = self.extract() self.mark() self.inc() # Skip # # The comment itself while not self.end() and not self._current.is_nl() and self.inc(): pass comment = self.extract() self.mark() break elif c in " \t\r": self.inc() else: raise self.parse_error(UnexpectedCharError, (c)) 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() return comment_ws, comment, trail def _parse_key_value( self, parse_comment=False, inline=True ): # type: (bool, bool) -> (Key, Item) # Leading indent self.mark() while self._current.is_spaces() and self.inc(): pass indent = self.extract() # Key key = self._parse_key() if not key.key.strip(): raise self.parse_error(EmptyKeyError) self.mark() found_equals = self._current == "=" while self._current.is_kv_sep() and self.inc(): if self._current == "=": if found_equals: raise self.parse_error(UnexpectedCharError, ("=",)) else: found_equals = True pass key.sep = self.extract() # Value val = self._parse_value() # Comment if parse_comment: cws, comment, trail = self._parse_comment_trail() meta = val.trivia meta.comment_ws = cws meta.comment = comment meta.trail = trail else: val.trivia.trail = "" val.trivia.indent = indent return key, val def _parse_key(self): # type: () -> Key """ Parses a Key at the current position; WS before the key must be exhausted first at the callsite. """ if self._current in "\"'": return self._parse_quoted_key() else: return self._parse_bare_key() def _parse_quoted_key(self): # type: () -> Key """ Parses a key enclosed in either single or double quotes. """ quote_style = self._current key_type = None dotted = False for t in KeyType: if t.value == quote_style: key_type = t break if key_type is None: raise RuntimeError("Should not have entered _parse_quoted_key()") self.inc() self.mark() while self._current != quote_style and self.inc(): pass key = self.extract() if self._current == ".": self.inc() dotted = True key += "." + self._parse_key().as_string() key_type = KeyType.Bare else: self.inc() return Key(key, key_type, "", dotted) def _parse_bare_key(self): # type: () -> Key """ Parses a bare key. """ key_type = None dotted = False self.mark() while self._current.is_bare_key_char() and self.inc(): pass key = self.extract() if self._current == ".": self.inc() dotted = True key += "." + self._parse_key().as_string() key_type = KeyType.Bare return Key(key, key_type, "", dotted) def _handle_dotted_key( self, container, key, value ): # type: (Container, Key) -> None names = tuple(self._split_table_name(key.key)) name = names[0] name._dotted = True if name in container: table = container.item(name) else: table = Table(Container(True), Trivia(), False, is_super_table=True) 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.item(_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 """ Attempts to parse a value at the current position. """ self.mark() trivia = Trivia() c = self._current if c == '"': return self._parse_basic_string() elif c == "'": return self._parse_literal_string() elif c == "t" and self._src[self._idx :].startswith("true"): # Boolean: true self.inc_n(4) return Bool(True, trivia) elif c == "f" and self._src[self._idx :].startswith("false"): # Boolean: true self.inc_n(5) return Bool(False, trivia) elif c == "[": # Array elems = [] # type: List[Item] self.inc() while self._current != "]": self.mark() while self._current.is_ws() or self._current == ",": self.inc() if self._idx != self._marker: elems.append(Whitespace(self.extract())) if self._current == "]": break if self._current == "#": cws, comment, trail = self._parse_comment_trail() next_ = Comment(Trivia("", cws, comment, trail)) else: next_ = self._parse_value() elems.append(next_) self.inc() try: res = Array(elems, trivia) except ValueError: raise self.parse_error(MixedArrayTypesError) if res.is_homogeneous(): return res raise self.parse_error(MixedArrayTypesError) elif c == "{": # Inline table elems = Container(True) self.inc() while self._current != "}": self.mark() while self._current.is_spaces() or self._current == ",": self.inc() if self._idx != self._marker: ws = self.extract().lstrip(",") if ws: elems.append(None, Whitespace(ws)) if self._current == "}": break key, val = self._parse_key_value(False, inline=True) elems.append(key, val) self.inc() return InlineTable(elems, trivia) elif c in string.digits + "+-" or self._peek(4) in { "+inf", "-inf", "inf", "+nan", "-nan", "nan", }: # Integer, Float, Date, Time or DateTime while self._current not in " \t\n\r#,]}" and self.inc(): pass raw = self.extract() item = self._parse_number(raw, trivia) if item is not None: return item try: res = parse_rfc3339(raw) except ValueError: res = None if res is None: raise self.parse_error(InvalidNumberOrDateError) if isinstance(res, datetime.datetime): return DateTime(res, trivia, raw) elif isinstance(res, datetime.time): return Time(res, trivia, raw) elif isinstance(res, datetime.date): return Date(res, trivia, raw) else: raise self.parse_error(InvalidNumberOrDateError) else: raise self.parse_error(UnexpectedCharError, (c)) def _parse_number(self, raw, trivia): # type: (str, 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") and not raw.startswith(("0.", "0o", "0x", "0b")) ): return if raw.startswith(("0o", "0x", "0b")) and sign: return digits = "[0-9]" base = 10 if raw.startswith("0b"): digits = "[01]" base = 2 elif raw.startswith("0o"): digits = "[0-7]" base = 8 elif raw.startswith("0x"): digits = "[0-9a-f]" base = 16 # Underscores should be surrounded by digits clean = re.sub("(?i)(?<={})_(?={})".format(digits, digits), "", raw) if "_" in clean: return if clean.endswith("."): return try: return Integer(int(sign + clean, base), trivia, sign + raw) except ValueError: try: return Float(float(sign + clean), trivia, sign + raw) except ValueError: return def _parse_literal_string(self): # type: () -> Item return self._parse_string("'") def _parse_basic_string(self): # type: () -> Item return self._parse_string('"') def _parse_string(self, delim): # type: (str) -> Item multiline = False value = "" if delim == "'": str_type = StringType.SLL else: str_type = StringType.SLB # Skip opening delim if not self.inc(): return self.parse_error(UnexpectedEofError) if self._current == delim: self.inc() if self._current == delim: multiline = True if delim == "'": str_type = StringType.MLL else: str_type = StringType.MLB if not self.inc(): return self.parse_error(UnexpectedEofError) else: # Empty string return String(str_type, "", "", Trivia()) self.mark() if self._current == "\n": # The first new line should be discarded self.inc() previous = None escaped = False while True: if ( previous != "\\" or previous == "\\" and (escaped or str_type.is_literal()) ) and self._current == delim: val = self.extract() if multiline: stop = True for _ in range(3): if self._current != delim: # Not a triple quote, leave in result as-is. stop = False # Adding back the quote character value += delim break self.inc() # TODO: Handle EOF if not stop: continue else: self.inc() return String(str_type, value, val, Trivia()) else: if previous == "\\" and self._current.is_ws() and multiline: while self._current.is_ws(): previous = self._current self.inc() continue if self._current == delim: continue if previous == "\\": if self._current == "\\" and not escaped: if not str_type.is_literal(): escaped = True else: value += self._current previous = self._current if not self.inc(): raise self.parse_error(UnexpectedEofError) continue elif self._current in _escaped and not escaped: if not str_type.is_literal(): value = value[:-1] value += _escaped[self._current] else: value += self._current elif self._current in {"u", "U"} and not escaped: # Maybe unicode u, ue = self._peek_unicode(self._current == "U") if u is not None: value = value[:-1] value += u self.inc_n(len(ue)) else: if not escaped and not str_type.is_literal(): raise self.parse_error( InvalidCharInStringError, (self._current,) ) value += self._current else: if not escaped and not str_type.is_literal(): raise self.parse_error( InvalidCharInStringError, (self._current,) ) value += self._current if self._current.is_ws() and multiline and not escaped: continue else: value += self._current if escaped: escaped = False previous = self._current if not self.inc(): raise self.parse_error(UnexpectedEofError) if previous == "\\" and self._current.is_ws() and multiline: value = value[:-1] def _parse_table( self, parent_name=None ): # type: (Optional[str]) -> Tuple[Key, Union[Table, AoT]] """ Parses a table element. """ if self._current != "[": raise self.parse_error( InternalParserError, ("_parse_table() called on non-bracket character.",), ) indent = self.extract() self.inc() # Skip opening bracket if self.end(): raise self.parse_error(UnexpectedEofError) is_aot = False if self._current == "[": if not self.inc(): raise self.parse_error(UnexpectedEofError) is_aot = True # Key self.mark() while self._current != "]" and self.inc(): if self.end(): raise self.parse_error(UnexpectedEofError) pass name = self.extract() if not name.strip(): raise self.parse_error(EmptyTableNameError) key = Key(name, sep="") name_parts = tuple(self._split_table_name(name)) missing_table = False if parent_name: parent_name_parts = tuple(self._split_table_name(parent_name)) else: parent_name_parts = tuple() if len(name_parts) > len(parent_name_parts) + 1: missing_table = True name_parts = name_parts[len(parent_name_parts) :] values = Container(True) self.inc() # Skip closing bracket if is_aot: # TODO: Verify close bracket self.inc() cws, comment, trail = self._parse_comment_trail() result = Null() if len(name_parts) > 1: if missing_table: # Missing super table # i.e. a table initialized like this: [foo.bar] # without initializing [foo] # # So we have to create the parent tables table = Table( Container(True), Trivia(indent, cws, comment, trail), is_aot and name_parts[0].key in self._aot_stack, is_super_table=True, name=name_parts[0].key, ) 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, ) if is_aot and i == len(name_parts[1:]) - 1: table.append(_name, AoT([child], name=table.name, parsed=True)) else: table.append(_name, child) table = child values = table.value else: if name_parts: key = name_parts[0] while not self.end(): item = self._parse_item() 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(values, _key, item) else: values.append(_key, item) else: if self._current == "[": is_aot_next, name_next = self._peek_table() if self._is_child(name, name_next): key_next, table_next = self._parse_table(name) values.append(key_next, table_next) # Picking up any sibling while not self.end(): _, name_next = self._peek_table() if not self._is_child(name, name_next): break key_next, table_next = self._parse_table(name) values.append(key_next, table_next) break else: raise self.parse_error( InternalParserError, ("_parse_item() returned None on a non-bracket character.",), ) if isinstance(result, Null): result = Table( values, Trivia(indent, cws, comment, trail), is_aot, name=name, display_name=name, ) if is_aot and (not self._aot_stack or name != self._aot_stack[-1]): result = self._parse_aot(result, name) return key, result def _peek_table(self): # type: () -> Tuple[bool, str] """ Peeks ahead non-intrusively by cloning then restoring the initial state of the parser. Returns the name of the table about to be parsed, as well as whether it is part of an AoT. """ # Save initial state idx = self._save_idx() marker = self._marker if self._current != "[": raise self.parse_error( InternalParserError, ("_peek_table() entered on non-bracket character",) ) # AoT self.inc() is_aot = False if self._current == "[": self.inc() is_aot = True self.mark() while self._current != "]" and self.inc(): table_name = self.extract() # Restore initial state self._restore_idx(*idx) self._marker = marker return is_aot, table_name def _parse_aot(self, first, name_first): # type: (Table, str) -> AoT """ Parses all siblings of the provided table first and bundles them into an AoT. """ payload = [first] self._aot_stack.append(name_first) while not self.end(): is_aot_next, name_next = self._peek_table() if is_aot_next and name_next == name_first: _, table = self._parse_table(name_first) payload.append(table) else: break self._aot_stack.pop() return AoT(payload, parsed=True) def _peek(self, n): # type: (int) -> str """ Peeks ahead n characters. n is the max number of characters that will be peeked. """ idx = self._save_idx() buf = "" for _ in range(n): if self._current not in " \t\n\r#,]}": buf += self._current self.inc() continue break self._restore_idx(*idx) return buf def _peek_unicode(self, is_long): # type: () -> Tuple[bool, str] """ Peeks ahead non-intrusively by cloning then restoring the initial state of the parser. Returns the unicode value is it's a valid one else None. """ # Save initial state idx = self._save_idx() marker = self._marker if self._current not in {"u", "U"}: raise self.parse_error( InternalParserError, ("_peek_unicode() entered on non-unicode value") ) # AoT self.inc() # Dropping prefix self.mark() if is_long: chars = 8 else: chars = 4 if not self.inc_n(chars): value, extracted = None, None else: extracted = self.extract() try: value = chr(int(extracted, 16)) except ValueError: value = None # Restore initial state self._restore_idx(*idx) self._marker = marker return value, extracted PK!F^tomlkit/toml_char.pyimport string from ._compat import unicode class TOMLChar(unicode): def __init__(self, c): super(TOMLChar, self).__init__() if len(self) > 1: raise ValueError("A TOML character must be of length 1") def is_bare_key_char(self): # type: () -> bool """ Whether the character is a valid bare key name or not. """ return self in string.ascii_letters + string.digits + "-" + "_" def is_kv_sep(self): # type: () -> bool """ Whether the character is a valid key/value separator ot not. """ return self in "= \t" def is_int_float_char(self): # type: () -> bool """ Whether the character if a valid integer or float value character or not. """ return self in string.digits + "+" + "-" + "_" + "." + "e" def is_ws(self): # type: () -> bool """ Whether the character is a whitespace character or not. """ return self in " \t\r\n" def is_nl(self): # type: () -> bool """ Whether the character is a new line character or not. """ return self in "\n\r" def is_spaces(self): # type: () -> bool """ Whether the character is a space or not """ return self in " \t" PK!q@ggtomlkit/toml_document.pyfrom .container import Container class TOMLDocument(Container): """ A TOML document. """ PK!P==tomlkit/toml_file.pyimport io from typing import Any from typing import Dict from .api import loads from .toml_document import TOMLDocument class TOMLFile(object): """ Represents a TOML file. """ def __init__(self, path): # type: (str) -> None self._path = path def read(self): # type: () -> TOMLDocument with io.open(self._path, encoding="utf-8") as f: return loads(f.read()) def write(self, data): # type: (TOMLDocument) -> None with io.open(self._path, "w", encoding="utf-8") as f: f.write(data.as_string()) PK!5&&tomlkit-0.4.5.dist-info/LICENSECopyright (c) 2018 Sébastien Eustace Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H,kXZtomlkit-0.4.5.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]OYE͕WQmfK_> wPK!H2, tomlkit-0.4.5.dist-info/METADATAXn6`[%iZI,Y&]yFMKE5ўc/Po;<0<7:*N~1No!jwS$ 1XL\{NKˑ{dy`"ʹJH7BT\P9.nL9⹑"eoPH{,;T{G!Vz]~z 쫓n ݢ]-:բ{1s9^`gYս1V5k&' e㒧'}:;Ztww8:1INOA߲Z|wEѦ%n<0Q6^KLǬݧF{-P`yR#RӼgD?`HRsQtk>A=6F(.09l&(ŒfIeP{DfUxa\Df\#fTK GXp'fSi@(ln$S3 G.#qG0@(v"ǽ G4^ *X}犅ZT\"6:6*a79%1⻌3 Ftc+ȡ ފifD kX>`e_u~4Be*ݔ'Ns؇$CJua%CMTRJlztgeLup읞HI={4bţܳU m^@>aY ) {JtS'оuYyF ZNy[[^H}ݯ0m%\; VD_;J`(S=Fy~^LI3Ι=sL4YN$htC{v*FY֮d$F˽*ʨ]Dcc㳅qᲦ-`Ǥ D4A@h \F ^BaBxkzUEP,K5b #.1澏c0#~ױ yRoo[l]:3Tmj7$OX^U< Mo􂇿Բ-;0|l&;\"}+V竰caET.k EX R;B:ev7jTڮQtP߽396G+cqsoҖ´P?!ý~{[+A0! q{I"P۾KE>iF_:G5K ҺY:o wYley6PvONO! X'cA6ōh>.Zlo\I z><ȾzQYI% =!_PK!*Ktomlkit/__init__.pyPK!Otomlkit/_compat.pyPK!+ + 3tomlkit/_utils.pyPK! #tomlkit/api.pyPK!sAA/tomlkit/container.pyPK!5PPqtomlkit/exceptions.pyPK!v]M n nftomlkit/items.pyPK!徕~~tomlkit/parser.pyPK!F^mtomlkit/toml_char.pyPK!q@ggrtomlkit/toml_document.pyPK!P==stomlkit/toml_file.pyPK!5&&utomlkit-0.4.5.dist-info/LICENSEPK!H,kXZ\ztomlkit-0.4.5.dist-info/WHEELPK!H2, ztomlkit-0.4.5.dist-info/METADATAPK!H8=gɁtomlkit-0.4.5.dist-info/RECORDPKĄ