PK!PLbtomlkit/__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 table from .api import time from .api import value from .api import ws PK!?RKtomlkit/_compat.pyimport 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 else: unicode = str 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!U}7? tomlkit/_utils.pyimport re from datetime import date from datetime import datetime from datetime import time from datetime import timedelta 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") PK!+/eetomlkit/api.pyimport datetime as _datetime from typing import Any from typing import Dict from typing import Tuple from ._utils import parse_rfc3339 from .container import Container 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 String from .items import StringType from .items import Table from .items import Time from .items import Trivia from .items import Whitespace from .parser import Parser from .toml_document import TOMLDocument as _TOMLDocument 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. """ 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 Integer(int(raw), Trivia(), raw) def float_(raw): # type: (str) -> Float return Float(float(raw), Trivia(), raw) def boolean(raw): # type: (str) -> Bool return Bool(raw == "true", Trivia()) def string(raw): # type: (str) -> String return String(StringType.SLB, raw, raw, Trivia()) def date(raw): # type: (str) -> Date value = parse_rfc3339(raw) if not isinstance(value, _datetime.date): raise ValueError("date() only accepts date strings.") return Date(value, Trivia(), raw) def time(raw): # type: (str) -> Time value = parse_rfc3339(raw) if not isinstance(value, _datetime.time): raise ValueError("time() only accepts time strings.") return Time(value, Trivia(), raw) def datetime(raw): # type: (str) -> DateTime value = parse_rfc3339(raw) if not isinstance(value, _datetime.datetime): raise ValueError("datetime() only accepts datetime strings.") return DateTime(value, Trivia(), raw) def array(raw=None): # type: (str) -> Array if raw is None: raw = "[]" return value(raw) def table(): # type: () -> Table return Table(Container(), Trivia(indent="\n"), 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) def nl(): # type: (src) -> Whitespace return ws("\n") def comment(string): # type: (str) -> Comment return Comment(Trivia(comment_ws=" ", comment="# " + string)) def item(value): # type: (Any) -> Item if isinstance(value, bool): return boolean(str(value).lower()) elif isinstance(value, int): return integer(str(value)) elif isinstance(value, float): return float_(str(value)) elif isinstance(value, list): value = "[{}]".format(", ".join([item(v).as_string() for v in value])) return array(value) elif isinstance(value, str): return string(value) elif isinstance(value, _datetime.datetime): return datetime(value.isoformat().replace("+00:00", "Z")) elif isinstance(value, _datetime.date): return date(value.isoformat()) elif isinstance(value, _datetime.time): return time(value.isoformat()) raise ValueError("Invalid type {}".format(type(value))) PK!bрfftomlkit/container.pyfrom __future__ import unicode_literals from typing import Dict from typing import Generator from typing import List from typing import Optional from typing import Tuple from ._compat import decode 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 Trivia from .items import Whitespace class Container(dict): """ A container for items within a TOMLDocument. """ def __init__(self): # type: () -> None self._map = {} # type: Dict[Key, int] self._body = [] # type: List[Tuple[Optional[Key], Item]] @property def body(self): # type: () -> List[Tuple[Optional[Key], Item]] return self._body @property def value(self): # type: () -> Dict[Any, Any] return {k: v for k, v in self.items()} def add( self, key, item=None ): # type: (Union[Key, Item, str], Optional[Item]) -> Item """ 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: (Key, Item) -> None from .api import item as _item 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 != key.key: item.name = key.key self._map[key] = len(self._body) self._body.append((key, item)) return item def remove(self, key): # type: (Key) -> None 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()) 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: if isinstance(v, Table): if prefix is not None: k = Key(prefix + "." + k.key) open_, close = "[", "]" if v.is_aot_element(): open_, close = "[[", "]]" cur = "{}{}{}{}{}{}{}{}".format( v.trivia.indent, open_, decode(k.as_string()), close, v.trivia.comment_ws, decode(v.trivia.comment), v.trivia.trail, v.as_string(prefix=prefix), ) elif isinstance(v, AoT): if prefix is not None: k = Key(prefix + "." + k.key) cur = "" key = decode(k.as_string()) for table in v.body: cur += "{}[[{}]]{}{}{}".format( table.trivia.indent, key, table.trivia.comment_ws, decode(table.trivia.comment), table.trivia.trail, ) cur += table.as_string(prefix=k.key) else: cur = "{}{}{}{}{}{}{}".format( v.trivia.indent, decode(k.as_string()), k.sep, decode(v.as_string()), v.trivia.comment_ws, decode(v.trivia.comment), v.trivia.trail, ) else: cur = v.as_string() s += cur return s # Dictionary methods def keys(self): # type: () -> Generator[Key] 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._body: if k is None: continue yield k.key, v.value def __contains__(self, key): # type: (Key) -> bool return key in self._map def __getitem__(self, key): # type: (Key) -> 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].value def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None if 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: (Key, Key, Item) -> None 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, Key, Item) -> None k, v = self._body[idx] self._map[new_key] = self._map.pop(k) self._body[idx] = (new_key, 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 PK!g:b b tomlkit/exceptions.pyfrom typing import Optional class ParseError(ValueError): """ 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 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(InvalidNumberOrDateError, self).__init__(line, col, message=message) class UnexpectedEofError(ParseError): """ An error that indicates a bug in the parser. """ def __init__(self, line, col): # type: (int, int) -> None message = "Unexpected end of file" super(InvalidNumberOrDateError, self).__init__(line, col, message=message) class NonExistentKey(KeyError): """ A non-existent key was used. """ def __init__(self, key): message = 'Key "{}" does not exist.'.format(key) super(NonExistentKey, self).__init__(message) PK!& : :tomlkit/items.pyfrom __future__ import unicode_literals import os import re from datetime import date from datetime import datetime from datetime import time from enum import Enum from typing import List from typing import Optional from ._compat import decode class StringType(Enum): SLB = '"' MLB = '"""' SLL = "'" 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): # type: (str) -> None self.t = t or KeyType.Bare self.sep = sep or " = " self.key = k @property def delimiter(self): # type: () -> str return self.t.value 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 return self.key == other.key 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, inline=True): # type: (str, bool) -> 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 __str__(self): # type: () -> str return str(self.value) def __repr__(self): # type: () -> str return "<{} {}>".format(self.__class__.__name__, self.as_string()) class Whitespace(Item): """ A whitespace literal. """ def __init__(self, s): # type: (str) -> None self._s = s @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 as_string(self): # type: () -> str return self._s def __repr__(self): # type: () -> str return "<{} {}>".format(self.__class__.__name__, repr(self._s)) 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(Item): """ An integer literal. """ def __init__(self, value, trivia, raw): # type: (int, Trivia, str) -> None super(Integer, self).__init__(trivia) self._value = value self._raw = raw @property def discriminant(self): # type: () -> int return 2 @property def value(self): # type: () -> int return self._value def as_string(self): # type: () -> str return self._raw class Float(Item): """ A float literal. """ def __init__(self, value, trivia, raw): # type: (float, Trivia, str) -> None super(Float, self).__init__(trivia) self._value = value self._raw = raw @property def discriminant(self): # type: () -> int return 3 @property def value(self): # type: () -> float return self._value def as_string(self): # type: () -> str return 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() class DateTime(Item): """ A datetime literal. """ def __init__(self, value, trivia, raw): # type: (datetime, Trivia, str) -> None super(DateTime, self).__init__(trivia) self._value = value self._raw = raw @property def discriminant(self): # type: () -> int return 5 @property def value(self): # type: () -> datetime return self._value def as_string(self): # type: () -> str return self._raw class Date(Item): """ A date literal. """ def __init__(self, value, trivia, raw): # type: (date, Trivia, str) -> None super(Date, self).__init__(trivia) self._value = value self._raw = raw @property def discriminant(self): # type: () -> int return 6 @property def value(self): # type: () -> date return self._value def as_string(self): # type: () -> str return self._raw class Time(Item): """ A time literal. """ def __init__(self, value, trivia, raw): # type: (time, Trivia, str) -> None super(Time, self).__init__(trivia) self._value = value self._raw = raw @property def discriminant(self): # type: () -> int return 7 @property def value(self): # type: () -> time return self._value def as_string(self): # type: () -> str return self._raw class Array(Item): """ An array literal """ def __init__(self, value, trivia): # type: (list, Trivia) -> None super(Array, self).__init__(trivia) self._value = value @property def discriminant(self): # type: () -> int return 8 @property def value(self): # type: () -> list return [ v.value for v in self._value if not isinstance(v, (Whitespace, Comment)) ] def is_homogeneous(self): # type: () -> bool if not self._value: 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(item.as_string() for item in self._value)) class Table(Item): """ A table literal. """ def __init__( self, value, trivia, is_aot_element, name=None ): # type: (tomlkit.container.Container, Trivia, bool) -> None super(Table, self).__init__(trivia) self.name = name self._value = value self._is_aot_element = is_aot_element @property def value(self): # type: () -> tomlkit.container.Container return self._value @property def discriminant(self): # type: () -> int return 9 @property def value(self): # type: () -> dict return self._value def add(self, key, item=None): # type: (Key, Item) -> 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: (Key, Item) -> Item """ Appends a (key, item) to the table. """ item = self._value.append(key, item) m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if not m: return item indent = m.group(1) if not isinstance(item, Whitespace): if isinstance(item, Table): indent = "\n" + indent item.trivia.indent = indent + item.trivia.indent return item def remove(self, key): # type: (Key) -> None self._value.remove(key) def is_aot_element(self): # type: () -> bool return self._is_aot_element def as_string(self, prefix=None): # type: () -> str if prefix is not None: if self.name is not None: prefix = prefix + "." + self.name elif self.name is not None: prefix = self.name 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 __repr__(self): # type: () -> str return "" def keys(self): # type: () -> Generator[Key] 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 __contains__(self, key): # type: (Key) -> bool return key in self._value def __getitem__(self, key): # type: (Key) -> str return self._value[key] def __setitem__(self, key, value): # type: (Key, Item) -> str self.append(key, value) def __delitem__(self, key): # type: (Key) -> str self.remove(key) class InlineTable(Item): """ An inline table literal. """ def __init__( self, value, trivia ): # type: (tomlkit.container.Container, Trivia) -> None super(InlineTable, self).__init__(trivia) self._value = value @property def discriminant(self): # type: () -> int return 10 @property def value(self): # type: () -> Dict return self._value def append(self, key, item): # type: (Key, Item) -> None """ Appends a (key, item) to the table. """ return self._value.append(key, item) def remove(self, key): # type: (Key) -> None self._value.remove(key) def as_string(self): # type: () -> str buf = "{" for i, (k, v) in enumerate(self._value.body): if k is None: buf += v.as_string() if i == len(self._value.body) - 1: buf = buf.rstrip(", ") continue buf += "{}{} = {}{}{}".format( v.trivia.indent, k.as_string(), v.as_string(), v.trivia.comment, v.trivia.trail, ) if i != len(self._value.body) - 1: buf += ", " buf += "}" return buf def keys(self): # type: () -> Generator[Key] 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 __contains__(self, key): # type: (Key) -> bool return key in self._value def __getitem__(self, key): # type: (Key) -> str return self._value[key] def __setitem__(self, key, value): # type: (Key, Item) -> str self.append(key, value) def __delitem__(self, key): # type: (Key) -> str self.remove(key) class String(Item): """ A string literal. """ def __init__( self, t, value, original, trivia ): # type: (StringType, str, original, Trivia) -> None super(String, self).__init__(trivia) self._t = t self._value = value self._original = original @property def discriminant(self): # type: () -> int return 11 @property def value(self): # type: () -> str return self._value def as_string(self): # type: () -> str return "{}{}{}".format(self._t.value, decode(self._original), self._t.value) class AoT(Item): """ An array of table literal """ def __init__(self, body, name=None): # type: (List[Table]) -> None self.name = None self._body = body @property def body(self): # type: () -> List[Table] return self._body @property def trivia(self): # type: () -> Trivia raise RuntimeError("Called trivia on a non-value Item variant.") @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 self._body.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 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 "" PK!TNTNtomlkit/parser.py# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime import itertools import string from copy import copy from typing import Iterator from typing import Optional from typing import Tuple from ._compat import PY2 from ._compat import decode from ._utils import parse_rfc3339 from .container import Container from .exceptions import InvalidNumberOrDateError from .exceptions import MixedArrayTypesError from .exceptions import ParseError 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 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() # 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 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) 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. """ return child != parent and child.startswith(parent) 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: return itertools.tee(self._chars)[1], 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: break if self.end(): break while self._current.is_spaces() and self.inc(): pass 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): # type: (bool) -> (Key, Item) # Leading indent self.mark() while self._current.is_spaces() and self.inc(): pass indent = self.extract() # Key key = self._parse_key() self.mark() while self._current.is_kv_sep() and self.inc(): 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 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() self.inc() return Key(key, key_type, "") def _parse_bare_key(self): # type: () -> Key """ Parses a bare key. """ self.mark() while self._current.is_bare_key_char() and self.inc(): pass key = self.extract() return Key(key, sep="") 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() res = Array(elems, trivia) if res.is_homogeneous(): return res raise self.parse_error(MixedArrayTypesError) elif c == "{": # Inline table elems = Container() self.inc() while self._current != "}": if self._current.is_ws() or self._current == ",": self.inc() continue key, val = self._parse_key_value(False) elems.append(key, val) self.inc() return InlineTable(elems, trivia) elif c in string.digits + "+" + "-": # 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: 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 if len(raw) > 1 and raw.startswith("0") and not raw.startswith("0."): return # Underscores should be surrounded by digits # TODO clean = "".join([c for c in raw if raw not in "_ "]) try: return Integer(int(clean), trivia, raw) except ValueError: try: return Float(float(clean), trivia, 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 # TODO: handle escaping multiline = False 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() previous = None while True: if previous and previous != "\\" and self._current == delim: val = self.extract() if multiline: for _ in range(3): if self._current != delim: # Not a triple quote, leave in result as-is. continue self.inc() # TODO: Handle EOF else: self.inc() return String(str_type, val, val, Trivia()) else: previous = self._current if not self.inc(): return self.parse_error(UnexpectedEofError) def _parse_table(self): # type: (Optional[str]) -> Tuple[Key, Item] """ Parses a table element. """ indent = self.extract() self.inc() # Skip opening bracket 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(): pass name = self.extract() key = Key(name, sep="") self.inc() # Skip closing bracket if is_aot: # TODO: Verify close bracket self.inc() cws, comment, trail = self._parse_comment_trail() result = Null() values = Container() while not self.end(): item = self._parse_item() if item: _key, item = item if not self._merge_ws(item, values): values.append(_key, item) else: if self._current == "[": _, name_next = self._peek_table() if self._is_child(name, name_next): key_next, table_next = self._parse_table() key_next = Key(key_next.key[len(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() key_next = Key(key_next.key[len(name + ".") :]) values.append(key_next, table_next) else: table = Table( values, Trivia(indent, cws, comment, trail), is_aot ) result = table if is_aot and ( not self._aot_stack or name != self._aot_stack[-1] ): result = self._parse_aot(table, name) 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) 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: (Item, str) -> Item """ 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() payload.append(table) else: break self._aot_stack.pop() return AoT(payload) PK!Wz!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 me 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!H)ȉeVXtomlkit-0.1.0.dist-info/WHEEL 1 0 нRn>Z(8_@ cfM6߅CUr$o­cbaP>PK!HLwL tomlkit-0.1.0.dist-info/METADATAWn6O)fMzM `43jZm6QT{}GJͲE*wǻ;O4ـ.ErNKeJ ai*r2<7Je3~'jb fot*۹ڼΔ0n"VˉdV@v \DVɌ. +"Y2*BVJwR!A/ooa'|?G臏Q"BM*">0ύ1c-:rf̈4llVC?T?|{>'u| ;2+cxq 罰+QTHYEߔ='Y) E珝66wCt ,"rάlb.cYYk~|`|6[R|HQR ᾚc͢%YȅOn$ᰒ"b#pGD>2K;SqIH0ğWJ`lʪ+ώoERhP3yuT:WSS| yYPژR1=I_pi(odE hCVq*u̗ߌȗs:[XJ1=rj\6ewJgwUjJ%Zcs`4.#6՚6&t܅BHFuI},:*C?PpNbԁDu9D]R9d,LciNI0pFFqkd dVڨ"zC+TL4c*Ωt p-,TRw` wJ\Vm F'3wNQZKy=YK,(Uؽ.aw@D9bBMZ"0F,v[V=1>c,FO;bfpA|pШjdȑJ XV[ot"w  v;n:I*Y8TJhȝi cxǤ rbsfi} AUmf"S]{ }SN6QzGzK<2ˏ^#;QjX*q DJR"xt?kwڽt:?*x1 %0_Lۗc̘@{ })S̐݀ ==>Iת>Z{NɌ*LY͎ 8UbrUt,ET%S%XF%U4YX' 6N vJit N6:[{W966Gtŵf k؀5UlL[|Y𯏎b?ӛyPek>_AoЬZe= |p!!?u g*Ncx m8}ǒF' &^+zD1M I v<7̵f:\lb;30̑ 'h6zaO2g"Տi)էh&*oUhf<PK!Hvtomlkit-0.1.0.dist-info/RECORDuI@}~ t bXdH(8 2hA(>E s﹏jRa3qi/&<U2#lj\(sg?IXe@V IKŽՂpzSшN)V&Խd*xI?xjK~"ܤr,Iҵ}Z{++4dx(x@ڶ IQq x_9))\blvȽ7y7Zgqn+>ڈe?e7?Ӽ|k7NKAzތ`g#=Vco ӼCc ,r2(JsLgsv+-`1`8(#-Ikhiv-!;)Uk"^Wq8&sK* 9$ >@ԫ|&{٤Q etf^wQL)~>2K{[νW`ϬWvPu^g4MNPiǯ}^Uz[,>G{⦸RWu$uEHqw–Y;Pw{.1LzyݺIQEDe ¾{0?~PK!PLbtomlkit/__init__.pyPK!?RKtomlkit/_compat.pyPK!U}7? tomlkit/_utils.pyPK!+/ee tomlkit/api.pyPK!bрffE0tomlkit/container.pyPK!g:b b Gtomlkit/exceptions.pyPK!& : :rQtomlkit/items.pyPK!TNTNtomlkit/parser.pyPK!Wz!-tomlkit/toml_char.pyPK!q@gg~tomlkit/toml_document.pyPK!P==tomlkit/toml_file.pyPK!H)ȉeVXtomlkit-0.1.0.dist-info/WHEELPK!HLwL tomlkit-0.1.0.dist-info/METADATAPK!Hvtomlkit-0.1.0.dist-info/RECORDPKr