pax_global_header00006660000000000000000000000064131620742720014516gustar00rootroot0000000000000052 comment=98954c4e682ada3cb73bbe12e90b7b2178885a2f toml-0.9.3/000077500000000000000000000000001316207427200125025ustar00rootroot00000000000000toml-0.9.3/.flake8000066400000000000000000000005111316207427200136520ustar00rootroot00000000000000[flake8] exclude = # Do not track .git dir .git, # Do not track virtualenv dir venv, # Ignore folders in gitignore dist, build, # Do not track pycache dirs __pycache__, max-line-length = 80 ignore = # Output config: show-source = True statistics = True tee = True output-file = .flake8.log toml-0.9.3/.gitignore000066400000000000000000000001501316207427200144660ustar00rootroot00000000000000# Filetypes *~ *.html *.pyc # Distutils/Setuptools build/ dist/ *.egg-info # Flake8 Stuff .flake8.log toml-0.9.3/.travis.yml000066400000000000000000000006501316207427200146140ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.3" - "3.4" - "3.5" - "3.6" - "pypy" notifications: email: false addons: apt: packages: - golang before_install: - export GOPATH=~/go - go get github.com/uiri/toml-test - pip install flake8 install: - python setup.py install - chmod +x ./tests/*.sh script: - ~/go/bin/toml-test ./tests/decoding_test.sh - python ./tests/api_test.py - ./tests/flake8.sh toml-0.9.3/CONTRIBUTING000066400000000000000000000016531316207427200143410ustar00rootroot00000000000000************ Contributing ************ Issues and Pull Requests are always welcome. Thank you in advance for your contribution! Reporting Issues ================ Before reporting an issue, please test the issue using the latest development version to see if your issue has been fixed since the latest release. Testing ======= There is a ``decoding_test.py`` script in the *tests/* directory which acts as a harness in order to allow ``toml`` to be used with the toml test suite, written (unfortunately) in Go. Directions ---------- 1. Install `Go `_ (AKA golang) 2. Get the toml-test suite from `here `_ and follow the instructions under the **Try it out** section of the README. 3. Test your changes for both versions of Python: * For Python 2, use ``~/go/bin/toml-test ./tests/decoding_test2.sh`` * For Python 3, use ``~/go/bin/toml-test ./tests/decoding_test3.sh`` toml-0.9.3/LICENSE000066400000000000000000000022761316207427200135160ustar00rootroot00000000000000The MIT License Copyright 2013-2017 Uiri Noyb Copyright 2015-2016 Julien Enselme Copyright 2016 Google Inc. Copyright 2017 Samuel Vasko Copyright 2017 Nate Prewitt Copyright 2017 Jack Evans 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.toml-0.9.3/MANIFEST.in000066400000000000000000000001041316207427200142330ustar00rootroot00000000000000include toml.py include setup.py include README.rst include LICENSE toml-0.9.3/README.rst000066400000000000000000000104051316207427200141710ustar00rootroot00000000000000**** TOML **** .. image:: https://badge.fury.io/py/toml.svg :target: https://badge.fury.io/py/toml .. image:: https://travis-ci.org/uiri/toml.svg?branch=master :target: https://travis-ci.org/uiri/toml A Python library for parsing and creating `TOML `_. The module passes `the TOML test suite `_ which is a fork of `BurntSushi's TOML test suite `_. See also: * `The TOML Standard `_ * `The current TOML specification `_ Installation ============ To install the latest release on `PyPi `_, simply run: :: pip install toml Or to install the latest development version, run: :: git clone https://github.com/uiri/toml.git cd toml python setup.py install Quick Tutorial ============== *toml.loads* takes in a string containing standard TOML-formatted data and returns a dictionary containing the parsed data. .. code:: pycon >>> import toml >>> toml_string = """ ... # This is a TOML document. ... ... title = "TOML Example" ... ... [owner] ... name = "Tom Preston-Werner" ... dob = 1979-05-27T07:32:00-08:00 # First class dates ... ... [database] ... server = "192.168.1.1" ... ports = [ 8001, 8001, 8002 ] ... connection_max = 5000 ... enabled = true ... ... [servers] ... ... # Indentation (tabs and/or spaces) is allowed but not required ... [servers.alpha] ... ip = "10.0.0.1" ... dc = "eqdc10" ... ... [servers.beta] ... ip = "10.0.0.2" ... dc = "eqdc10" ... ... [clients] ... data = [ ["gamma", "delta"], [1, 2] ] ... ... # Line breaks are OK when inside arrays ... hosts = [ ... "alpha", ... "omega" ... ] ... """ >>> parsed_toml = toml.loads(toml_string) *toml.dumps* takes a dictionary and returns a string containing the corresponding TOML-formatted data. .. code:: pycon >>> new_toml_string = toml.dumps(parsed_toml) >>> print(new_toml_string) title = "TOML Example" [owner] name = "Tom Preston-Werner" dob = 1979-05-27T07:32:00Z [database] server = "192.168.1.1" ports = [ 8001, 8001, 8002,] connection_max = 5000 enabled = true [clients] data = [ [ "gamma", "delta",], [ 1, 2,],] hosts = [ "alpha", "omega",] [servers.alpha] ip = "10.0.0.1" dc = "eqdc10" [servers.beta] ip = "10.0.0.2" dc = "eqdc10" For more functions, view the API Reference below. API Reference ============= ``toml.load(f, _dict=dict)`` Parse a file or a list of files as TOML and return a dictionary. :Args: * ``f``: A path to a file, list of filepaths (to be read into single object) or a file descriptor * ``_dict``: The class of the dictionary object to be returned :Returns: A dictionary (or object ``_dict``) containing parsed TOML data :Raises: * ``TypeError``: When ``f`` is an invalid type or is a list containing invalid types * ``TomlDecodeError``: When an error occurs while decoding the file(s) ``toml.loads(s, _dict=dict)`` Parse a TOML-formatted string to a dictionary. :Args: * ``s``: The TOML-formatted string to be parsed * ``_dict``: Specifies the class of the returned toml dictionary :Returns: A dictionary (or object ``_dict``) containing parsed TOML data :Raises: * ``TypeError``: When a non-string object is passed * ``TomlDecodeError``: When an error occurs while decoding the TOML-formatted string ``toml.dump(o, f)`` Write a dictionary to a file containing TOML-formatted data :Args: * ``o``: An object to be converted into TOML * ``f``: A File descriptor where the TOML-formatted output should be stored :Returns: A string containing the TOML-formatted data corresponding to object ``o`` :Raises: * ``TypeError``: When anything other than file descriptor is passed ``toml.dumps(o)`` Create a TOML-formatted string from an input object :Args: * ``o``: An object to be converted into TOML :Returns: A string containing the TOML-formatted data corresponding to object ``o`` Licensing ========= This project is released under the terms of the MIT Open Source License. View *LICENSE.txt* for more information. toml-0.9.3/RELEASE.rst000066400000000000000000000010311316207427200143070ustar00rootroot00000000000000**** Releasing **** New versions of the TOML library are released on PyPI and installable via pip. A new release is marked via a git tag. Version numbering loosely follows semantic versioning. 1.0.0 will be the first version to support TOML 1.0.0. TOML still has yet to hit TOML 1.0.0 so its release is postponed indefinitely. The version number is recorded in the source code (toml.py) itself as the __version__ variable. The LICENSE Copyright notice should be up to date with a year-author pair for each significant contributor. toml-0.9.3/examples/000077500000000000000000000000001316207427200143205ustar00rootroot00000000000000toml-0.9.3/examples/example-v0.4.0.toml000066400000000000000000000121721316207427200174760ustar00rootroot00000000000000################################################################################ ## Comment # Speak your mind with the hash symbol. They go from the symbol to the end of # the line. ################################################################################ ## Table # Tables (also known as hash tables or dictionaries) are collections of # key/value pairs. They appear in square brackets on a line by themselves. [table] key = "value" # Yeah, you can do this. # Nested tables are denoted by table names with dots in them. Name your tables # whatever crap you please, just don't use #, ., [ or ]. [table.subtable] key = "another value" # You don't need to specify all the super-tables if you don't want to. TOML # knows how to do it for you. # [x] you # [x.y] don't # [x.y.z] need these [x.y.z.w] # for this to work ################################################################################ ## Inline Table # Inline tables provide a more compact syntax for expressing tables. They are # especially useful for grouped data that can otherwise quickly become verbose. # Inline tables are enclosed in curly braces `{` and `}`. No newlines are # allowed between the curly braces unless they are valid within a value. [table.inline] name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } ################################################################################ ## String # There are four ways to express strings: basic, multi-line basic, literal, and # multi-line literal. All strings must contain only valid UTF-8 characters. [string.basic] basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." [string.multiline] # The following strings are byte-for-byte equivalent: key1 = "One\nTwo" key2 = """One\nTwo""" key3 = """ One Two""" [string.multiline.continued] # The following strings are byte-for-byte equivalent: key1 = "The quick brown fox jumps over the lazy dog." key2 = """ The quick brown \ fox jumps over \ the lazy dog.""" key3 = """\ The quick brown \ fox jumps over \ the lazy dog.\ """ [string.literal] # What you see is what you get. winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' regex = '<\i\c*\s*>' [string.literal.multiline] regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' The first newline is trimmed in raw strings. All other whitespace is preserved. ''' ################################################################################ ## Integer # Integers are whole numbers. Positive numbers may be prefixed with a plus sign. # Negative numbers are prefixed with a minus sign. [integer] key1 = +99 key2 = 42 key3 = 0 key4 = -17 [integer.underscores] # For large numbers, you may use underscores to enhance readability. Each # underscore must be surrounded by at least one digit. key1 = 1_000 key2 = 5_349_221 key3 = 1_2_3_4_5 # valid but inadvisable ################################################################################ ## Float # A float consists of an integer part (which may be prefixed with a plus or # minus sign) followed by a fractional part and/or an exponent part. [float.fractional] key1 = +1.0 key2 = 3.1415 key3 = -0.01 [float.exponent] key1 = 5e+22 key2 = 1e6 key3 = -2E-2 [float.both] key = 6.626e-34 [float.underscores] key1 = 9_224_617.445_991_228_313 key2 = 1e1_000 ################################################################################ ## Boolean # Booleans are just the tokens you're used to. Always lowercase. [boolean] True = true False = false ################################################################################ ## Datetime # Datetimes are RFC 3339 dates. [datetime] key1 = 1979-05-27T07:32:00Z key2 = 1979-05-27T00:32:00-07:00 key3 = 1979-05-27T00:32:00.999999-07:00 ################################################################################ ## Array # Arrays are square brackets with other primitives inside. Whitespace is # ignored. Elements are separated by commas. Data types may not be mixed. [array] key1 = [ 1, 2, 3 ] key2 = [ "red", "yellow", "green" ] key3 = [ [ 1, 2 ], [3, 4, 5] ] key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok # Arrays can also be multiline. So in addition to ignoring whitespace, arrays # also ignore newlines between the brackets. Terminating commas are ok before # the closing bracket. key5 = [ 1, 2, 3 ] key6 = [ 1, 2, # this is ok ] ################################################################################ ## Array of Tables # These can be expressed by using a table name in double brackets. Each table # with the same double bracketed name will be an element in the array. The # tables are inserted in the order encountered. [[products]] name = "Hammer" sku = 738594937 [[products]] [[products]] name = "Nail" sku = 284758393 color = "gray" # You can create nested arrays of tables as well. [[fruit]] name = "apple" [fruit.physical] color = "red" shape = "round" [[fruit.variety]] name = "red delicious" [[fruit.variety]] name = "granny smith" [[fruit]] name = "banana" [[fruit.variety]] name = "plantain" toml-0.9.3/setup.py000066400000000000000000000010161316207427200142120ustar00rootroot00000000000000try: from setuptools import setup except ImportError: from distutils.core import setup import toml with open("README.rst") as readme_file: readme_string = readme_file.read() setup( name="toml", version=toml.__version__, description="Python Library for Tom's Obvious, Minimal Language", author="Uiri Noyb", author_email="uiri@xqz.ca", url="https://github.com/uiri/toml", py_modules=['toml'], license="License :: OSI Approved :: MIT License", long_description=readme_string, ) toml-0.9.3/test.toml000066400000000000000000000022211316207427200143530ustar00rootroot00000000000000# This is a TOML document. Boom. a = "C:\\Users\\n" title = "TOML Example" the-void = [[[[[]]]]] mixed = [[1, +2], ["a", "b"], [1.0, 2.0]] avogadro = 6.23e23 [owner] name = "Tom \\ / Preston-Werner" organization = "GitHub" bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." dob = 1979-05-27T07:32:00Z # First class dates? Why not? [database] server = "192.168.1.1" ports = [ 8001, 8001, 8002 ] connection_max = 5000 enabled = true test = [["a"], ["b"], ["c"]] [servers] # You can indent as you please. Tabs or spaces. TOML don't care. [servers.alpha] ip = "10.0.0.1" dc = "eqdc10" [servers.beta] ip = "\u000a\u1000\u1000\u0002" dc = "eqdc10" [clients] data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it # Line breaks are OK when inside arrays hosts = [ "alpha", "omega" ] [[fruit]] name = "apple" [fruit.physical] color = "red" shape = "round" [[fruit.variety]] name = "red delicious" #[fruit.variety] # name = "granny smith" [[fruit]] name = "banana" [[fruit.variety]] name = "plantain" [[fruit.variety]] name = "whatever" thing=34 "l.mao" = 42 toml-0.9.3/tests/000077500000000000000000000000001316207427200136445ustar00rootroot00000000000000toml-0.9.3/tests/api_test.py000066400000000000000000000024621316207427200160320ustar00rootroot00000000000000if __name__ == "__main__" and __package__ is None: from os import sys, path sys.path.insert(0, path.dirname(path.dirname(__file__))) from toml import TomlDecodeError # noqa: E402 import toml # noqa: E402 import warnings # noqa: E402 class TestDict(dict): pass try: FNFError = FileNotFoundError except NameError: FNFError = IOError TEST_STR = """ [a] b = 1 c = 2 """ INVALID_TOML = """ strings-and-ints = ["hi", 42] """ TEST_DICT = {"a": {"b": 1, "c": 2}} o = toml.loads(TEST_STR) assert(o == toml.loads(toml.dumps(o))) assert(isinstance(toml.loads(TEST_STR, _dict=TestDict), TestDict)) try: toml.loads(2) # Expected TypeError assert(False) except TypeError: pass try: toml.loads(INVALID_TOML) # Expected TomlDecodeError assert(False) except TomlDecodeError: pass try: toml.load(2) # Expected TypeError assert(False) except TypeError: pass try: toml.load([]) # Expected FileNotFoundError or IOError (according to Python version) assert(False) except FNFError: pass toml.load(["test.toml"]) with warnings.catch_warnings(record=True) as w: toml.load(["test.toml", "nonexist.toml"]) # Expect 1 warning for the non existent toml file assert(len(w) == 1) s = toml.dumps(TEST_DICT) assert(s == toml.dumps(toml.loads(s))) toml-0.9.3/tests/decoding_test.py000077500000000000000000000027611316207427200170420ustar00rootroot00000000000000"""Decodes toml and outputs it as tagged JSON""" import datetime import json import sys import toml if sys.version_info < (3,): _range = xrange # noqa: F821 iteritems = dict.iteritems else: unicode = str _range = range basestring = str unichr = chr iteritems = dict.items long = int def tag(value): if isinstance(value, dict): d = {} for k, v in iteritems(value): d[k] = tag(v) return d elif isinstance(value, list): a = [] for v in value: a.append(tag(v)) try: a[0]["value"] except KeyError: return a except IndexError: pass return {'type': 'array', 'value': a} elif isinstance(value, basestring): return {'type': 'string', 'value': value} elif isinstance(value, bool): return {'type': 'bool', 'value': str(value).lower()} elif isinstance(value, int): return {'type': 'integer', 'value': str(value)} elif isinstance(value, long): return {'type': 'integer', 'value': str(value)} elif isinstance(value, float): return {'type': 'float', 'value': repr(value)} elif isinstance(value, datetime.datetime): sdate = value.strftime('%Y-%m-%dT%H:%M:%SZ') return {'type': 'datetime', 'value': sdate} assert False, 'Unknown type: %s' % type(value) if __name__ == '__main__': tdata = toml.loads(sys.stdin.read()) tagged = tag(tdata) print(json.dumps(tagged)) toml-0.9.3/tests/decoding_test.sh000077500000000000000000000000511316207427200170120ustar00rootroot00000000000000#!/bin/sh python tests/decoding_test.py toml-0.9.3/tests/decoding_test2.sh000077500000000000000000000001021316207427200170710ustar00rootroot00000000000000#!/bin/sh export PYTHONPATH=`pwd` python2 tests/decoding_test.py toml-0.9.3/tests/decoding_test3.sh000077500000000000000000000001021316207427200170720ustar00rootroot00000000000000#!/bin/sh export PYTHONPATH=`pwd` python3 tests/decoding_test.py toml-0.9.3/tests/flake8.sh000077500000000000000000000001141316207427200153510ustar00rootroot00000000000000#!/bin/sh if [ -z `python --version 2>&1 | grep 2.6` ]; then flake8 fi toml-0.9.3/toml.py000066400000000000000000000742761316207427200140470ustar00rootroot00000000000000"""Python module which parses and emits TOML. Released under the MIT license. """ import re import io import datetime from os import linesep __version__ = "0.9.3" __spec__ = "0.4.0" class TomlDecodeError(Exception): """Base toml Exception / Error.""" pass class TomlTz(datetime.tzinfo): def __init__(self, toml_offset): if toml_offset == "Z": self._raw_offset = "+00:00" else: self._raw_offset = toml_offset self._sign = -1 if self._raw_offset[0] == '-' else 1 self._hours = int(self._raw_offset[1:3]) self._minutes = int(self._raw_offset[4:6]) def tzname(self, dt): return "UTC" + self._raw_offset def utcoffset(self, dt): return self._sign * datetime.timedelta(hours=self._hours, minutes=self._minutes) def dst(self, dt): return datetime.timedelta(0) class InlineTableDict(object): """Sentinel subclass of dict for inline tables.""" def _get_empty_inline_table(_dict): class DynamicInlineTableDict(_dict, InlineTableDict): """Concrete sentinel subclass for inline tables. It is a subclass of _dict which is passed in dynamically at load time It is also a subclass of InlineTableDict """ return DynamicInlineTableDict() try: _range = xrange except NameError: unicode = str _range = range basestring = str unichr = chr try: FNFError = FileNotFoundError except NameError: FNFError = IOError def load(f, _dict=dict): """Parses named file or files as toml and returns a dictionary Args: f: Path to the file to open, array of files to read into single dict or a file descriptor _dict: (optional) Specifies the class of the returned toml dictionary Returns: Parsed toml file represented as a dictionary Raises: TypeError -- When f is invalid type TomlDecodeError: Error while decoding toml IOError / FileNotFoundError -- When an array with no valid (existing) (Python 2 / Python 3) file paths is passed """ if isinstance(f, basestring): with io.open(f, encoding='utf-8') as ffile: return loads(ffile.read(), _dict) elif isinstance(f, list): from os import path as op from warnings import warn if not [path for path in f if op.exists(path)]: error_msg = "Load expects a list to contain filenames only." error_msg += linesep error_msg += ("The list needs to contain the path of at least one " "existing file.") raise FNFError(error_msg) d = _dict() for l in f: if op.exists(l): d.update(load(l)) else: warn("Non-existent filename in list with at least one valid " "filename") return d else: try: return loads(f.read(), _dict) except AttributeError: raise TypeError("You can only load a file descriptor, filename or " "list") _groupname_re = re.compile(r'^[A-Za-z0-9_-]+$') def loads(s, _dict=dict): """Parses string as toml Args: s: String to be parsed _dict: (optional) Specifies the class of the returned toml dictionary Returns: Parsed toml file represented as a dictionary Raises: TypeError: When a non-string is passed TomlDecodeError: Error while decoding toml """ implicitgroups = [] retval = _dict() currentlevel = retval if not isinstance(s, basestring): raise TypeError("Expecting something like a string") if not isinstance(s, unicode): s = s.decode('utf8') sl = list(s) openarr = 0 openstring = False openstrchar = "" multilinestr = False arrayoftables = False beginline = True keygroup = False keyname = 0 for i, item in enumerate(sl): if item == '\r' and sl[i + 1] == '\n': sl[i] = ' ' continue if keyname: if item == '\n': raise TomlDecodeError("Key name found without value." " Reached end of line.") if openstring: if item == openstrchar: keyname = 2 openstring = False openstrchar = "" continue elif keyname == 1: if item.isspace(): keyname = 2 continue elif item.isalnum() or item == '_' or item == '-': continue elif keyname == 2 and item.isspace(): continue if item == '=': keyname = 0 else: raise TomlDecodeError("Found invalid character in key name: '" + item + "'. Try quoting the key name.") if item == "'" and openstrchar != '"': k = 1 try: while sl[i - k] == "'": k += 1 if k == 3: break except IndexError: pass if k == 3: multilinestr = not multilinestr openstring = multilinestr else: openstring = not openstring if openstring: openstrchar = "'" else: openstrchar = "" if item == '"' and openstrchar != "'": oddbackslash = False k = 1 tripquote = False try: while sl[i - k] == '"': k += 1 if k == 3: tripquote = True break if k == 1 or (k == 3 and tripquote): while sl[i - k] == '\\': oddbackslash = not oddbackslash k += 1 except IndexError: pass if not oddbackslash: if tripquote: multilinestr = not multilinestr openstring = multilinestr else: openstring = not openstring if openstring: openstrchar = '"' else: openstrchar = "" if item == '#' and (not openstring and not keygroup and not arrayoftables): j = i try: while sl[j] != '\n': sl[j] = ' ' j += 1 except IndexError: break if item == '[' and (not openstring and not keygroup and not arrayoftables): if beginline: if sl[i + 1] == '[': arrayoftables = True else: keygroup = True else: openarr += 1 if item == ']' and not openstring: if keygroup: keygroup = False elif arrayoftables: if sl[i - 1] == ']': arrayoftables = False else: openarr -= 1 if item == '\n': if openstring or multilinestr: if not multilinestr: raise TomlDecodeError("Unbalanced quotes") if ((sl[i - 1] == "'" or sl[i - 1] == '"') and ( sl[i - 2] == sl[i - 1])): sl[i] = sl[i - 1] if sl[i - 3] == sl[i - 1]: sl[i - 3] = ' ' elif openarr: sl[i] = ' ' else: beginline = True elif beginline and sl[i] != ' ' and sl[i] != '\t': beginline = False if not keygroup and not arrayoftables: if sl[i] == '=': raise TomlDecodeError("Found empty keyname. ") keyname = 1 s = ''.join(sl) s = s.split('\n') multikey = None multilinestr = "" multibackslash = False for line in s: if not multilinestr or multibackslash or '\n' not in multilinestr: line = line.strip() if line == "" and (not multikey or multibackslash): continue if multikey: if multibackslash: multilinestr += line else: multilinestr += line multibackslash = False if len(line) > 2 and (line[-1] == multilinestr[0] and line[-2] == multilinestr[0] and line[-3] == multilinestr[0]): value, vtype = _load_value(multilinestr, _dict) currentlevel[multikey] = value multikey = None multilinestr = "" else: k = len(multilinestr) - 1 while k > -1 and multilinestr[k] == '\\': multibackslash = not multibackslash k -= 1 if multibackslash: multilinestr = multilinestr[:-1] else: multilinestr += "\n" continue if line[0] == '[': arrayoftables = False if line[1] == '[': arrayoftables = True line = line[2:].split(']]', 1) else: line = line[1:].split(']', 1) if line[1].strip() != "": raise TomlDecodeError("Key group not on a line by itself.") groups = line[0].split('.') i = 0 while i < len(groups): groups[i] = groups[i].strip() if groups[i][0] == '"' or groups[i][0] == "'": groupstr = groups[i] j = i + 1 while not groupstr[0] == groupstr[-1]: j += 1 groupstr = '.'.join(groups[i:j]) groups[i] = groupstr[1:-1] groups[i + 1:j] = [] else: if not _groupname_re.match(groups[i]): raise TomlDecodeError("Invalid group name '" + groups[i] + "'. Try quoting it.") i += 1 currentlevel = retval for i in _range(len(groups)): group = groups[i] if group == "": raise TomlDecodeError("Can't have a keygroup with an empty " "name") try: currentlevel[group] if i == len(groups) - 1: if group in implicitgroups: implicitgroups.remove(group) if arrayoftables: raise TomlDecodeError("An implicitly defined " "table can't be an array") elif arrayoftables: currentlevel[group].append(_dict()) else: raise TomlDecodeError("What? " + group + " already exists?" + str(currentlevel)) except TypeError: currentlevel = currentlevel[-1] try: currentlevel[group] except KeyError: currentlevel[group] = _dict() if i == len(groups) - 1 and arrayoftables: currentlevel[group] = [_dict()] except KeyError: if i != len(groups) - 1: implicitgroups.append(group) currentlevel[group] = _dict() if i == len(groups) - 1 and arrayoftables: currentlevel[group] = [_dict()] currentlevel = currentlevel[group] if arrayoftables: try: currentlevel = currentlevel[-1] except KeyError: pass elif line[0] == "{": if line[-1] != "}": raise TomlDecodeError("Line breaks are not allowed in inline" "objects") _load_inline_object(line, currentlevel, _dict, multikey, multibackslash) elif "=" in line: ret = _load_line(line, currentlevel, _dict, multikey, multibackslash) if ret is not None: multikey, multilinestr, multibackslash = ret return retval def _load_inline_object(line, currentlevel, _dict, multikey=False, multibackslash=False): candidate_groups = line[1:-1].split(",") groups = [] if len(candidate_groups) == 1 and not candidate_groups[0].strip(): candidate_groups.pop() while len(candidate_groups) > 0: candidate_group = candidate_groups.pop(0) try: _, value = candidate_group.split('=', 1) except ValueError: raise TomlDecodeError("Invalid inline table encountered") value = value.strip() if ((value[0] == value[-1] and value[0] in ('"', "'")) or ( value[0] in '-0123456789' or value in ('true', 'false') or (value[0] == "[" and value[-1] == "]"))): groups.append(candidate_group) else: candidate_groups[0] = candidate_group + "," + candidate_groups[0] for group in groups: status = _load_line(group, currentlevel, _dict, multikey, multibackslash) if status is not None: break # Matches a TOML number, which allows underscores for readability _number_with_underscores = re.compile('([0-9])(_([0-9]))*') def _strictly_valid_num(n): n = n.strip() if not n: return False if n[0] == '_': return False if n[-1] == '_': return False if "_." in n or "._" in n: return False if len(n) == 1: return True if n[0] == '0' and n[1] != '.': return False if n[0] == '+' or n[0] == '-': n = n[1:] if n[0] == '0' and n[1] != '.': return False if '__' in n: return False return True def _load_line(line, currentlevel, _dict, multikey, multibackslash): i = 1 pair = line.split('=', i) strictly_valid = _strictly_valid_num(pair[-1]) if _number_with_underscores.match(pair[-1]): pair[-1] = pair[-1].replace('_', '') while len(pair[-1]) and (pair[-1][0] != ' ' and pair[-1][0] != '\t' and pair[-1][0] != "'" and pair[-1][0] != '"' and pair[-1][0] != '[' and pair[-1][0] != '{' and pair[-1] != 'true' and pair[-1] != 'false'): try: float(pair[-1]) break except ValueError: pass if _load_date(pair[-1]) is not None: break i += 1 prev_val = pair[-1] pair = line.split('=', i) if prev_val == pair[-1]: raise TomlDecodeError("Invalid date or number") if strictly_valid: strictly_valid = _strictly_valid_num(pair[-1]) pair = ['='.join(pair[:-1]).strip(), pair[-1].strip()] if (pair[0][0] == '"' or pair[0][0] == "'") and \ (pair[0][-1] == '"' or pair[0][-1] == "'"): pair[0] = pair[0][1:-1] if len(pair[1]) > 2 and ((pair[1][0] == '"' or pair[1][0] == "'") and pair[1][1] == pair[1][0] and pair[1][2] == pair[1][0] and not (len(pair[1]) > 5 and pair[1][-1] == pair[1][0] and pair[1][-2] == pair[1][0] and pair[1][-3] == pair[1][0])): k = len(pair[1]) - 1 while k > -1 and pair[1][k] == '\\': multibackslash = not multibackslash k -= 1 if multibackslash: multilinestr = pair[1][:-1] else: multilinestr = pair[1] + "\n" multikey = pair[0] else: value, vtype = _load_value(pair[1], _dict, strictly_valid) try: currentlevel[pair[0]] raise TomlDecodeError("Duplicate keys!") except KeyError: if multikey: return multikey, multilinestr, multibackslash else: currentlevel[pair[0]] = value except: raise TomlDecodeError("Duplicate keys!") def _load_date(val): microsecond = 0 tz = None try: if len(val) > 19: if val[19] == '.': microsecond = int(val[20:26]) if len(val) > 26: tz = TomlTz(val[26:32]) else: tz = TomlTz(val[19:25]) except ValueError: tz = None try: d = datetime.datetime( int(val[:4]), int(val[5:7]), int(val[8:10]), int(val[11:13]), int(val[14:16]), int(val[17:19]), microsecond, tz) except ValueError: return None return d def _load_unicode_escapes(v, hexbytes, prefix): hexchars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'] skip = False i = len(v) - 1 while i > -1 and v[i] == '\\': skip = not skip i -= 1 for hx in hexbytes: if skip: skip = False i = len(hx) - 1 while i > -1 and hx[i] == '\\': skip = not skip i -= 1 v += prefix v += hx continue hxb = "" i = 0 hxblen = 4 if prefix == "\\U": hxblen = 8 while i < hxblen: try: if not hx[i].lower() in hexchars: raise IndexError("This is a hack") except IndexError: raise TomlDecodeError("Invalid escape sequence") hxb += hx[i].lower() i += 1 v += unichr(int(hxb, 16)) v += unicode(hx[len(hxb):]) return v # Unescape TOML string values. # content after the \ _escapes = ['0', 'b', 'f', 'n', 'r', 't', '"'] # What it should be replaced by _escapedchars = ['\0', '\b', '\f', '\n', '\r', '\t', '\"'] # Used for substitution _escape_to_escapedchars = dict(zip(_escapes, _escapedchars)) def _unescape(v): """Unescape characters in a TOML string.""" i = 0 backslash = False while i < len(v): if backslash: backslash = False if v[i] in _escapes: v = v[:i - 1] + _escape_to_escapedchars[v[i]] + v[i + 1:] elif v[i] == '\\': v = v[:i - 1] + v[i:] elif v[i] == 'u' or v[i] == 'U': i += 1 else: raise TomlDecodeError("Reserved escape sequence used") continue elif v[i] == '\\': backslash = True i += 1 return v def _load_value(v, _dict, strictly_valid=True): if not v: raise TomlDecodeError("Empty value is invalid") if v == 'true': return (True, "bool") elif v == 'false': return (False, "bool") elif v[0] == '"': testv = v[1:].split('"') if testv[0] == '' and testv[1] == '': testv = testv[2:-2] closed = False for tv in testv: if tv == '': closed = True else: oddbackslash = False try: i = -1 j = tv[i] while j == '\\': oddbackslash = not oddbackslash i -= 1 j = tv[i] except IndexError: pass if not oddbackslash: if closed: raise TomlDecodeError("Stuff after closed string. WTF?") else: closed = True escapeseqs = v.split('\\')[1:] backslash = False for i in escapeseqs: if i == '': backslash = not backslash else: if i[0] not in _escapes and (i[0] != 'u' and i[0] != 'U' and not backslash): raise TomlDecodeError("Reserved escape sequence used") if backslash: backslash = False for prefix in ["\\u", "\\U"]: if prefix in v: hexbytes = v.split(prefix) v = _load_unicode_escapes(hexbytes[0], hexbytes[1:], prefix) v = _unescape(v) if v[1] == '"' and (len(v) < 3 or v[1] == v[2]): v = v[2:-2] return (v[1:-1], "str") elif v[0] == "'": if v[1] == "'" and (len(v) < 3 or v[1] == v[2]): v = v[2:-2] return (v[1:-1], "str") elif v[0] == '[': return (_load_array(v, _dict), "array") elif v[0] == '{': inline_object = _get_empty_inline_table(_dict) _load_inline_object(v, inline_object, _dict) return (inline_object, "inline_object") else: parsed_date = _load_date(v) if parsed_date is not None: return (parsed_date, "date") if not strictly_valid: raise TomlDecodeError("Weirdness with leading zeroes or underscores" " in your number.") itype = "int" neg = False if v[0] == '-': neg = True v = v[1:] elif v[0] == '+': v = v[1:] v = v.replace('_', '') if '.' in v or 'e' in v or 'E' in v: if '.' in v and v.split('.', 1)[1] == '': raise TomlDecodeError("This float is missing digits after " "the point") if v[0] not in '0123456789': raise TomlDecodeError("This float doesn't have a leading digit") v = float(v) itype = "float" else: v = int(v) if neg: return (0 - v, itype) return (v, itype) def _load_array(a, _dict): atype = None retval = [] a = a.strip() if '[' not in a[1:-1] or "" != a[1:-1].split('[')[0].strip(): strarray = False tmpa = a[1:-1].strip() if tmpa != '' and (tmpa[0] == '"' or tmpa[0] == "'"): strarray = True if not a[1:-1].strip().startswith('{'): a = a[1:-1].split(',') else: # a is an inline object, we must find the matching parenthesis # to define groups new_a = [] start_group_index = 1 end_group_index = 2 in_str = False while end_group_index < len(a[1:]): if a[end_group_index] == '"' or a[end_group_index] == "'": in_str = not in_str if in_str or a[end_group_index] != '}': end_group_index += 1 continue # Increase end_group_index by 1 to get the closing bracket end_group_index += 1 new_a.append(a[start_group_index:end_group_index]) # The next start index is at least after the closing bracket, a # closing bracket can be followed by a comma since we are in # an array. start_group_index = end_group_index + 1 while (start_group_index < len(a[1:]) and a[start_group_index] != '{'): start_group_index += 1 end_group_index = start_group_index + 1 a = new_a b = 0 if strarray: while b < len(a) - 1: ab = a[b].strip() while ab[-1] != ab[0] or (ab[0] == ab[1] == ab[2] and ab[-2] != ab[0] and ab[-3] != ab[0]): a[b] = a[b] + ',' + a[b + 1] ab = a[b].strip() if b < len(a) - 2: a = a[:b + 1] + a[b + 2:] else: a = a[:b + 1] b += 1 else: al = list(a[1:-1]) a = [] openarr = 0 j = 0 for i in _range(len(al)): if al[i] == '[': openarr += 1 elif al[i] == ']': openarr -= 1 elif al[i] == ',' and not openarr: a.append(''.join(al[j:i])) j = i + 1 a.append(''.join(al[j:])) for i in _range(len(a)): a[i] = a[i].strip() if a[i] != '': nval, ntype = _load_value(a[i], _dict) if atype: if ntype != atype: raise TomlDecodeError("Not a homogeneous array") else: atype = ntype retval.append(nval) return retval def dump(o, f): """Writes out dict as toml to a file Args: o: Object to dump into toml f: File descriptor where the toml should be stored Returns: String containing the toml corresponding to dictionary Raises: TypeError: When anything other than file descriptor is passed """ if not f.write: raise TypeError("You can only dump an object to a file descriptor") d = dumps(o) f.write(d) return d def dumps(o, preserve=False): """Stringifies input dict as toml Args: o: Object to dump into toml preserve: Boolean parameter. If true, preserve inline tables. Returns: String containing the toml corresponding to dict """ retval = "" addtoretval, sections = _dump_sections(o, "") retval += addtoretval while sections != {}: newsections = {} for section in sections: addtoretval, addtosections = _dump_sections(sections[section], section, preserve) if addtoretval or (not addtoretval and not addtosections): if retval and retval[-2:] != "\n\n": retval += "\n" retval += "[" + section + "]\n" if addtoretval: retval += addtoretval for s in addtosections: newsections[section + "." + s] = addtosections[s] sections = newsections return retval def _dump_sections(o, sup, preserve=False): retstr = "" if sup != "" and sup[-1] != ".": sup += '.' retdict = o.__class__() arraystr = "" for section in o: section = str(section) qsection = section if not re.match(r'^[A-Za-z0-9_-]+$', section): if '"' in section: qsection = "'" + section + "'" else: qsection = '"' + section + '"' if not isinstance(o[section], dict): arrayoftables = False if isinstance(o[section], list): for a in o[section]: if isinstance(a, dict): arrayoftables = True if arrayoftables: for a in o[section]: arraytabstr = "\n" arraystr += "[[" + sup + qsection + "]]\n" s, d = _dump_sections(a, sup + qsection) if s: if s[0] == "[": arraytabstr += s else: arraystr += s while d != {}: newd = {} for dsec in d: s1, d1 = _dump_sections(d[dsec], sup + qsection + "." + dsec) if s1: arraytabstr += ("[" + sup + qsection + "." + dsec + "]\n") arraytabstr += s1 for s1 in d1: newd[dsec + "." + s1] = d1[s1] d = newd arraystr += arraytabstr else: if o[section] is not None: retstr += (qsection + " = " + str(_dump_value(o[section])) + '\n') elif preserve and isinstance(o[section], InlineTableDict): retstr += (section + " = " + _dump_inline_table(o[section])) else: retdict[qsection] = o[section] retstr += arraystr return (retstr, retdict) def _dump_inline_table(section): """Preserve inline table in its compact syntax instead of expanding into subsection. https://github.com/toml-lang/toml#user-content-inline-table """ retval = "" if isinstance(section, dict): val_list = [] for k, v in section.items(): val = _dump_inline_table(v) val_list.append(k + " = " + val) retval += "{ " + ", ".join(val_list) + " }\n" return retval else: return str(_dump_value(section)) def _dump_value(v): dump_funcs = { str: lambda: _dump_str(v), unicode: lambda: _dump_str(v), list: lambda: _dump_list(v), bool: lambda: str(v).lower(), float: lambda: _dump_float(v), datetime.datetime: lambda: v.isoformat(), } # Lookup function corresponding to v's type dump_fn = dump_funcs.get(type(v)) # Evaluate function (if it exists) else return v return dump_fn() if dump_fn is not None else v def _dump_str(v): v = "%r" % v if v[0] == 'u': v = v[1:] singlequote = v.startswith("'") v = v[1:-1] if singlequote: v = v.replace("\\'", "'") v = v.replace('"', '\\"') v = v.replace("\\x", "\\u00") return str('"' + v + '"') def _dump_list(v): t = [] retval = "[" for u in v: t.append(_dump_value(u)) while t != []: s = [] for u in t: if isinstance(u, list): for r in u: s.append(r) else: retval += " " + str(u) + "," t = s retval += "]" return retval def _dump_float(v): return "{0:.16g}".format(v).replace("e+0", "e+").replace("e-0", "e-") toml-0.9.3/toml.pyi000066400000000000000000000010631316207427200142000ustar00rootroot00000000000000from typing import Any, IO, Mapping, MutableMapping, Optional, Type, Union import datetime class TomlDecodeError(Exception): ... class TomlTz(datetime.tzinfo): def __init__(self, toml_offset: str) -> None: ... def load(f: Union[str, list, IO[str]], _dict: Type[MutableMapping[str, Any]] = ...) \ -> MutableMapping[str, Any]: ... def loads(s: str, _dict: Type[MutableMapping[str, Any]] = ...) \ -> MutableMapping[str, Any]: ... def dump(o: Mapping[str, Any], f: IO[str]) -> str: ... def dumps(o: Mapping[str, Any]) -> str: ...