pax_global_header00006660000000000000000000000064133552724420014521gustar00rootroot0000000000000052 comment=4935f616ef78c35a968b2473e806d7049eba9af1 toml-0.10.0/000077500000000000000000000000001335527244200125525ustar00rootroot00000000000000toml-0.10.0/.coveragearc000066400000000000000000000000661335527244200150360ustar00rootroot00000000000000[run] branch = True source = toml omit = tests/* toml-0.10.0/.flake8000066400000000000000000000005221335527244200137240ustar00rootroot00000000000000[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__, .tox, max-line-length = 80 ignore = # Output config: show-source = True statistics = True tee = True output-file = .flake8.log toml-0.10.0/.gitignore000066400000000000000000000024111335527244200145400ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log .static_storage/ .media/ local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # Emacs *~ \#*\# /.emacs.desktop /.emacs.desktop.lock *.elc auto-save-list tramp .\#* toml-0.10.0/.travis.yml000066400000000000000000000010741335527244200146650ustar00rootroot00000000000000language: python cache: pip python: - "2.7" - "3.4" - "3.5" - "3.6" - "pypy" matrix: include: - python: "3.7" dist: xenial sudo: true notifications: email: false addons: apt: packages: - git - golang before_install: - export GOPATH=~/go - go get github.com/BurntSushi/toml-test - git clone https://github.com/BurntSushi/toml-test install: - pip install tox-travis - python setup.py install - chmod +x ./tests/*.sh script: - ~/go/bin/toml-test ./tests/decoding_test.sh - tox - tox -e check after_success: - tox -e codecov toml-0.10.0/CONTRIBUTING000066400000000000000000000026451335527244200144130ustar00rootroot00000000000000************ 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 ======= ### Unit tests Unit tests can be run using [tox](https://tox.readthedocs.io/en/latest/). Simply `pip install tox` and you are ready to go. Tox creates required virual eniroments and installs necessary packages. tox This is not very practical for day to day use. To easily run tests in the current Python simply run. tox -e py We are using [pytest](https://docs.pytest.org/en/latest/) testing framework. You can pass parameters to it like this: tox -e py -- -vsx ### Decoding tests 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.10.0/LICENSE000066400000000000000000000023041335527244200135560ustar00rootroot00000000000000The MIT License Copyright 2013-2018 William Pearson 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.10.0/MANIFEST.in000066400000000000000000000001461335527244200143110ustar00rootroot00000000000000include LICENSE include README.rst include toml.pyi include tox.ini recursive-include tests *.py *.sh toml-0.10.0/README.rst000066400000000000000000000104311335527244200142400ustar00rootroot00000000000000**** 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 .. image:: https://img.shields.io/pypi/pyversions/toml.svg :target: https://pypi.org/project/toml/ A Python library for parsing and creating `TOML `_. The module passes `the TOML test suite `_. See also: * `The TOML Standard `_ * `The currently supported 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.10.0/RELEASE.rst000066400000000000000000000010531335527244200143630ustar00rootroot00000000000000********* 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.10.0/examples/000077500000000000000000000000001335527244200143705ustar00rootroot00000000000000toml-0.10.0/examples/example-v0.4.0.out000066400000000000000000000041111335527244200173740ustar00rootroot00000000000000{'table': {'key': 'value', 'subtable': {'key': 'another value'}, 'inline': {'name': {'first': 'Tom', 'last': 'Preston-Werner'}, 'point': {'x': 1, 'y': 2}}}, 'x': {'y': {'z': {'w': {}}}}, 'string': {'basic': {'basic': 'I\'m a string. "You can quote me". Name\tJosé\nLocation\tSF.'}, 'multiline': {'key1': 'One\nTwo', 'key2': 'One\nTwo', 'key3': 'One\nTwo', 'continued': {'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.'}}, 'literal': {'winpath': 'C:\\Users\\nodejs\\templates', 'winpath2': '\\\\ServerX\\admin$\\system32\\', 'quoted': 'Tom "Dubs" Preston-Werner', 'regex': '<\\i\\c*\\s*>', 'multiline': {'regex2': "I [dw]on't need \\d{2} apples", 'lines': 'The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n'}}}, 'integer': {'key1': 99, 'key2': 42, 'key3': 0, 'key4': -17, 'underscores': {'key1': 1000, 'key2': 5349221, 'key3': 12345}}, 'float': {'fractional': {'key1': 1.0, 'key2': 3.1415, 'key3': -0.01}, 'exponent': {'key1': 5e+22, 'key2': 1000000.0, 'key3': -0.02}, 'both': {'key': 6.626e-34}, 'underscores': {'key1': 9224617.445991227, 'key2': inf}}, 'boolean': {'True': True, 'False': False}, 'datetime': {'key1': datetime.datetime(1979, 5, 27, 7, 32, tzinfo=), 'key2': datetime.datetime(1979, 5, 27, 0, 32, tzinfo=), 'key3': datetime.datetime(1979, 5, 27, 0, 32, 0, 999999, tzinfo=)}, 'array': {'key1': [1, 2, 3], 'key2': ['red', 'yellow', 'green'], 'key3': [[1, 2], [3, 4, 5]], 'key4': [[1, 2], ['a', 'b', 'c']], 'key5': [1, 2, 3], 'key6': [1, 2]}, 'products': [{'name': 'Hammer', 'sku': 738594937}, {}, {'name': 'Nail', 'sku': 284758393, 'color': 'gray'}], 'fruit': [{'name': 'apple', 'physical': {'color': 'red', 'shape': 'round'}, 'variety': [{'name': 'red delicious'}, {'name': 'granny smith'}]}, {'name': 'banana', 'variety': [{'name': 'plantain'}]}]} toml-0.10.0/examples/example-v0.4.0.toml000066400000000000000000000124531335527244200175500ustar00rootroot00000000000000################################################################################ ## 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.If you include only the date portion of an # RFC 3339 formatted date-time, it will represent that entire day without # any relation to an offset or timezone. [datetime] key1 = 1979-05-27T07:32:00Z key2 = 1979-05-27T00:32:00-07:00 key3 = 1979-05-27T00:32:00.999999-07:00 key4 = 1979-05-27 ################################################################################ ## 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.10.0/setup.cfg000066400000000000000000000001021335527244200143640ustar00rootroot00000000000000[bdist_wheel] universal = True [metadata] license_file = LICENSE toml-0.10.0/setup.py000066400000000000000000000024631335527244200142710ustar00rootroot00000000000000try: 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="William Pearson", author_email="uiri@xqz.ca", url="https://github.com/uiri/toml", packages=['toml'], license="MIT", long_description=readme_string, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ] ) toml-0.10.0/test.toml000066400000000000000000000022211335527244200144230ustar00rootroot00000000000000# 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.10.0/tests/000077500000000000000000000000001335527244200137145ustar00rootroot00000000000000toml-0.10.0/tests/__init__.py000066400000000000000000000000001335527244200160130ustar00rootroot00000000000000toml-0.10.0/tests/decoding_test.py000077500000000000000000000033121335527244200171030ustar00rootroot00000000000000"""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): return {'type': 'datetime', 'value': value.isoformat() .replace('+00:00', 'Z')} elif isinstance(value, datetime.date): return {'type': 'date', 'value': value.isoformat()} elif isinstance(value, datetime.time): return {'type': 'time', 'value': value.strftime('%H:%M:%S.%f')} 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.10.0/tests/decoding_test.sh000077500000000000000000000000511335527244200170620ustar00rootroot00000000000000#!/bin/sh python tests/decoding_test.py toml-0.10.0/tests/decoding_test2.sh000077500000000000000000000001021335527244200171410ustar00rootroot00000000000000#!/bin/sh export PYTHONPATH=`pwd` python2 tests/decoding_test.py toml-0.10.0/tests/decoding_test3.sh000077500000000000000000000001021335527244200171420ustar00rootroot00000000000000#!/bin/sh export PYTHONPATH=`pwd` python3 tests/decoding_test.py toml-0.10.0/tests/test_api.py000066400000000000000000000100131335527244200160710ustar00rootroot00000000000000import toml import copy import pytest import os import sys from toml.decoder import InlineTableDict TEST_STR = """ [a]\r b = 1\r c = 2 """ TEST_DICT = {"a": {"b": 1, "c": 2}} def test_bug_148(): assert 'a = "\\u0064"\n' == toml.dumps({'a': '\\x64'}) assert 'a = "\\\\x64"\n' == toml.dumps({'a': '\\\\x64'}) assert 'a = "\\\\\\u0064"\n' == toml.dumps({'a': '\\\\\\x64'}) def test_bug_144(): if sys.version_info >= (3,): return bug_dict = {'username': '\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d'} round_trip_bug_dict = toml.loads(toml.dumps(bug_dict)) unicoded_bug_dict = {'username': bug_dict['username'].decode('utf-8')} assert round_trip_bug_dict == unicoded_bug_dict assert bug_dict['username'] == (round_trip_bug_dict['username'] .encode('utf-8')) def test_bug_196(): import datetime d = datetime.datetime.now() bug_dict = {'x': d} round_trip_bug_dict = toml.loads(toml.dumps(bug_dict)) assert round_trip_bug_dict == bug_dict assert round_trip_bug_dict['x'] == bug_dict['x'] def test_valid_tests(): valid_dir = "toml-test/tests/valid/" for f in os.listdir(valid_dir): if not f.endswith("toml"): continue toml.dumps(toml.load(open(os.path.join(valid_dir, f)))) def test__dict(): class TestDict(dict): pass assert isinstance(toml.loads( TEST_STR, _dict=TestDict), TestDict) def test_dict_decoder(): class TestDict(dict): pass test_dict_decoder = toml.TomlDecoder(TestDict) assert isinstance(toml.loads( TEST_STR, decoder=test_dict_decoder), TestDict) def test_inline_dict(): class TestDict(dict, InlineTableDict): pass encoder = toml.TomlPreserveInlineDictEncoder() t = copy.deepcopy(TEST_DICT) t['d'] = TestDict() t['d']['x'] = "abc" o = toml.loads(toml.dumps(t, encoder=encoder)) assert o == toml.loads(toml.dumps(o, encoder=encoder)) def test_array_sep(): encoder = toml.TomlArraySeparatorEncoder(separator=",\t") d = {"a": [1, 2, 3]} o = toml.loads(toml.dumps(d, encoder=encoder)) assert o == toml.loads(toml.dumps(o, encoder=encoder)) def test_ordered(): from toml import ordered as toml_ordered encoder = toml_ordered.TomlOrderedEncoder() decoder = toml_ordered.TomlOrderedDecoder() o = toml.loads(toml.dumps(TEST_DICT, encoder=encoder), decoder=decoder) assert o == toml.loads(toml.dumps(TEST_DICT, encoder=encoder), decoder=decoder) def test_tuple(): d = {"a": (3, 4)} o = toml.loads(toml.dumps(d)) assert o == toml.loads(toml.dumps(o)) def test_invalid_tests(): invalid_dir = "toml-test/tests/invalid/" for f in os.listdir(invalid_dir): if not f.endswith("toml"): continue with pytest.raises(toml.TomlDecodeError): toml.load(open(os.path.join(invalid_dir, f))) def test_exceptions(): with pytest.raises(TypeError): toml.loads(2) with pytest.raises(TypeError): toml.load(2) try: FileNotFoundError except NameError: # py2 FileNotFoundError = IOError with pytest.raises(FileNotFoundError): toml.load([]) class FakeFile(object): def __init__(self): self.written = "" def write(self, s): self.written += s return None def read(self): return self.written def test_dump(): f = FakeFile() g = FakeFile() h = FakeFile() toml.dump(TEST_DICT, f) toml.dump(toml.load(f), g) toml.dump(toml.load(g), h) assert g.written == h.written def test_paths(): toml.load("test.toml") import sys if (3, 4) <= sys.version_info: import pathlib p = pathlib.Path("test.toml") toml.load(p) def test_warnings(): # Expect 1 warning for the non existent toml file with pytest.warns(UserWarning): toml.load(["test.toml", "nonexist.toml"]) def test_commutativity(): o = toml.loads(toml.dumps(TEST_DICT)) assert o == toml.loads(toml.dumps(o)) toml-0.10.0/toml.pyi000066400000000000000000000010631335527244200142500ustar00rootroot00000000000000from 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: ... toml-0.10.0/toml/000077500000000000000000000000001335527244200135255ustar00rootroot00000000000000toml-0.10.0/toml/__init__.py000066400000000000000000000007671335527244200156500ustar00rootroot00000000000000"""Python module which parses and emits TOML. Released under the MIT license. """ from toml import encoder from toml import decoder __version__ = "0.10.0" _spec_ = "0.5.0" load = decoder.load loads = decoder.loads TomlDecoder = decoder.TomlDecoder TomlDecodeError = decoder.TomlDecodeError dump = encoder.dump dumps = encoder.dumps TomlEncoder = encoder.TomlEncoder TomlArraySeparatorEncoder = encoder.TomlArraySeparatorEncoder TomlPreserveInlineDictEncoder = encoder.TomlPreserveInlineDictEncoder toml-0.10.0/toml/decoder.py000066400000000000000000001043571335527244200155160ustar00rootroot00000000000000import datetime import io from os import linesep import re import sys from toml.tz import TomlTz if sys.version_info < (3,): _range = xrange # noqa: F821 else: unicode = str _range = range basestring = str unichr = chr def _detect_pathlib_path(p): if (3, 4) <= sys.version_info: import pathlib if isinstance(p, pathlib.PurePath): return True return False def _ispath(p): if isinstance(p, basestring): return True return _detect_pathlib_path(p) def _getpath(p): if (3, 6) <= sys.version_info: import os return os.fspath(p) if _detect_pathlib_path(p): return str(p) return p try: FNFError = FileNotFoundError except NameError: FNFError = IOError TIME_RE = re.compile("([0-9]{2}):([0-9]{2}):([0-9]{2})(\.([0-9]{3,6}))?") class TomlDecodeError(ValueError): """Base toml Exception / Error.""" def __init__(self, msg, doc, pos): lineno = doc.count('\n', 0, pos) + 1 colno = pos - doc.rfind('\n', 0, pos) emsg = '{} (line {} column {} char {})'.format(msg, lineno, colno, pos) ValueError.__init__(self, emsg) self.msg = msg self.doc = doc self.pos = pos self.lineno = lineno self.colno = colno # 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] not in ['.', 'o', 'b', 'x']: return False if n[0] == '+' or n[0] == '-': n = n[1:] if len(n) > 1 and n[0] == '0' and n[1] != '.': return False if '__' in n: return False return True def load(f, _dict=dict, decoder=None): """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 _ispath(f): with io.open(_getpath(f), encoding='utf-8') as ffile: return loads(ffile.read(), _dict, decoder) 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) if decoder is None: decoder = TomlDecoder() d = decoder.get_empty_table() for l in f: if op.exists(l): d.update(load(l, _dict, decoder)) else: warn("Non-existent filename in list with at least one valid " "filename") return d else: try: return loads(f.read(), _dict, decoder) 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, decoder=None): """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 = [] if decoder is None: decoder = TomlDecoder(_dict) retval = decoder.get_empty_table() currentlevel = retval if not isinstance(s, basestring): raise TypeError("Expecting something like a string") if not isinstance(s, unicode): s = s.decode('utf8') original = s sl = list(s) openarr = 0 openstring = False openstrchar = "" multilinestr = False arrayoftables = False beginline = True keygroup = False dottedkey = 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.", original, i) if openstring: if item == openstrchar: keyname = 2 openstring = False openstrchar = "" continue elif keyname == 1: if item.isspace(): keyname = 2 continue elif item == '.': dottedkey = True continue elif item.isalnum() or item == '_' or item == '-': continue elif (dottedkey and sl[i - 1] == '.' and (item == '"' or item == "'")): openstring = True openstrchar = item continue elif keyname == 2: if item.isspace(): if dottedkey: nextitem = sl[i + 1] if not nextitem.isspace() and nextitem != '.': keyname = 1 continue if item == '.': dottedkey = True nextitem = sl[i + 1] if not nextitem.isspace() and nextitem != '.': keyname = 1 continue if item == '=': keyname = 0 dottedkey = False else: raise TomlDecodeError("Found invalid character in key name: '" + item + "'. Try quoting the key name.", original, i) 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 len(sl) > i + 1 and 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", original, i) 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. ", original, i) keyname = 1 s = ''.join(sl) s = s.split('\n') multikey = None multilinestr = "" multibackslash = False pos = 0 for idx, line in enumerate(s): if idx > 0: pos += len(s[idx - 1]) + 1 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]): try: value, vtype = decoder.load_value(multilinestr) except ValueError as err: raise TomlDecodeError(str(err), original, pos) 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 len(line) == 1: raise TomlDecodeError("Opening key group bracket on line by " "itself.", original, pos) if line[1] == '[': arrayoftables = True line = line[2:] splitstr = ']]' else: line = line[1:] splitstr = ']' i = 1 quotesplits = decoder._get_split_on_quotes(line) quoted = False for quotesplit in quotesplits: if not quoted and splitstr in quotesplit: break i += quotesplit.count(splitstr) quoted = not quoted line = line.split(splitstr, i) if len(line) < i + 1 or line[-1].strip() != "": raise TomlDecodeError("Key group not on a line by itself.", original, pos) groups = splitstr.join(line[:-1]).split('.') i = 0 while i < len(groups): groups[i] = groups[i].strip() if len(groups[i]) > 0 and (groups[i][0] == '"' or groups[i][0] == "'"): groupstr = groups[i] j = i + 1 while not groupstr[0] == groupstr[-1]: j += 1 if j > len(groups) + 2: raise TomlDecodeError("Invalid group name '" + groupstr + "' Something " + "went wrong.", original, pos) groupstr = '.'.join(groups[i:j]).strip() 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.", original, pos) 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", original, pos) 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", original, pos) elif arrayoftables: currentlevel[group].append(decoder.get_empty_table() ) else: raise TomlDecodeError("What? " + group + " already exists?" + str(currentlevel), original, pos) except TypeError: currentlevel = currentlevel[-1] if group not in currentlevel: currentlevel[group] = decoder.get_empty_table() if i == len(groups) - 1 and arrayoftables: currentlevel[group] = [decoder.get_empty_table()] except KeyError: if i != len(groups) - 1: implicitgroups.append(group) currentlevel[group] = decoder.get_empty_table() if i == len(groups) - 1 and arrayoftables: currentlevel[group] = [decoder.get_empty_table()] 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", original, pos) try: decoder.load_inline_object(line, currentlevel, multikey, multibackslash) except ValueError as err: raise TomlDecodeError(str(err), original, pos) elif "=" in line: try: ret = decoder.load_line(line, currentlevel, multikey, multibackslash) except ValueError as err: raise TomlDecodeError(str(err), original, pos) if ret is not None: multikey, multilinestr, multibackslash = ret return retval def _load_date(val): microsecond = 0 tz = None try: if len(val) > 19: if val[19] == '.': if val[-1].upper() == 'Z': subsecondval = val[20:-1] tzval = "Z" else: subsecondvalandtz = val[20:] if '+' in subsecondvalandtz: splitpoint = subsecondvalandtz.index('+') subsecondval = subsecondvalandtz[:splitpoint] tzval = subsecondvalandtz[splitpoint:] elif '-' in subsecondvalandtz: splitpoint = subsecondvalandtz.index('-') subsecondval = subsecondvalandtz[:splitpoint] tzval = subsecondvalandtz[splitpoint:] else: tzval = None subsecondval = subsecondvalandtz if tzval is not None: tz = TomlTz(tzval) microsecond = int(int(subsecondval) * (10 ** (6 - len(subsecondval)))) else: tz = TomlTz(val[19:]) except ValueError: tz = None if "-" not in val[1:]: return None try: if len(val) == 10: d = datetime.date( int(val[:4]), int(val[5:7]), int(val[8:10])) else: 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): 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 hxb = ''.join(hx[i:i + hxblen]).lower() if hxb.strip('0123456789abcdef'): raise ValueError("Invalid escape sequence: " + hxb) if hxb[0] == "d" and hxb[1].strip('01234567'): raise ValueError("Invalid escape sequence: " + hxb + ". Only scalar unicode points are allowed.") 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 ValueError("Reserved escape sequence used") continue elif v[i] == '\\': backslash = True i += 1 return v class InlineTableDict(object): """Sentinel subclass of dict for inline tables.""" class TomlDecoder(object): def __init__(self, _dict=dict): self._dict = _dict def get_empty_table(self): return self._dict() def get_empty_inline_table(self): class DynamicInlineTableDict(self._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() def load_inline_object(self, line, currentlevel, 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 ValueError("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] == "]") or (value[0] == '{' and value[-1] == '}'))): groups.append(candidate_group) elif len(candidate_groups) > 0: candidate_groups[0] = (candidate_group + "," + candidate_groups[0]) else: raise ValueError("Invalid inline table value encountered") for group in groups: status = self.load_line(group, currentlevel, multikey, multibackslash) if status is not None: break def _get_split_on_quotes(self, line): doublequotesplits = line.split('"') quoted = False quotesplits = [] if len(doublequotesplits) > 1 and "'" in doublequotesplits[0]: singlequotesplits = doublequotesplits[0].split("'") doublequotesplits = doublequotesplits[1:] while len(singlequotesplits) % 2 == 0 and len(doublequotesplits): singlequotesplits[-1] += '"' + doublequotesplits[0] doublequotesplits = doublequotesplits[1:] if "'" in singlequotesplits[-1]: singlequotesplits = (singlequotesplits[:-1] + singlequotesplits[-1].split("'")) quotesplits += singlequotesplits for doublequotesplit in doublequotesplits: if quoted: quotesplits.append(doublequotesplit) else: quotesplits += doublequotesplit.split("'") quoted = not quoted return quotesplits def load_line(self, line, currentlevel, multikey, multibackslash): i = 1 quotesplits = self._get_split_on_quotes(line) quoted = False for quotesplit in quotesplits: if not quoted and '=' in quotesplit: break i += quotesplit.count('=') quoted = not quoted 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 ValueError("Invalid date or number") if strictly_valid: strictly_valid = _strictly_valid_num(pair[-1]) pair = ['='.join(pair[:-1]).strip(), pair[-1].strip()] if '.' in pair[0]: if '"' in pair[0] or "'" in pair[0]: quotesplits = self._get_split_on_quotes(pair[0]) quoted = False levels = [] for quotesplit in quotesplits: if quoted: levels.append(quotesplit) else: levels += [level.strip() for level in quotesplit.split('.')] quoted = not quoted else: levels = pair[0].split('.') while levels[-1] == "": levels = levels[:-1] for level in levels[:-1]: if level == "": continue if level not in currentlevel: currentlevel[level] = self.get_empty_table() currentlevel = currentlevel[level] pair[0] = levels[-1].strip() elif (pair[0][0] == '"' or pair[0][0] == "'") and \ (pair[0][-1] == pair[0][0]): 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 = self.load_value(pair[1], strictly_valid) try: currentlevel[pair[0]] raise ValueError("Duplicate keys!") except TypeError: raise ValueError("Duplicate keys!") except KeyError: if multikey: return multikey, multilinestr, multibackslash else: currentlevel[pair[0]] = value def load_value(self, v, strictly_valid=True): if not v: raise ValueError("Empty value is invalid") if v == 'true': return (True, "bool") elif v == 'false': return (False, "bool") elif v[0] == '"' or v[0] == "'": quotechar = v[0] testv = v[1:].split(quotechar) triplequote = False triplequotecount = 0 if len(testv) > 1 and testv[0] == '' and testv[1] == '': testv = testv[2:] triplequote = True closed = False for tv in testv: if tv == '': if triplequote: triplequotecount += 1 else: 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 ValueError("Stuff after closed string. WTF?") else: if not triplequote or triplequotecount > 1: closed = True else: triplequotecount = 0 if quotechar == '"': 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 ValueError("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 len(v) > 1 and v[1] == quotechar and (len(v) < 3 or v[1] == v[2]): v = v[2:-2] return (v[1:-1], "str") elif v[0] == '[': return (self.load_array(v), "array") elif v[0] == '{': inline_object = self.get_empty_inline_table() self.load_inline_object(v, inline_object) return (inline_object, "inline_object") elif TIME_RE.match(v): h, m, s, _, ms = TIME_RE.match(v).groups() time = datetime.time(int(h), int(m), int(s), int(ms) if ms else 0) return (time, "time") else: parsed_date = _load_date(v) if parsed_date is not None: return (parsed_date, "date") if not strictly_valid: raise ValueError("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('_', '') lowerv = v.lower() if '.' in v or ('x' not in v and ('e' in v or 'E' in v)): if '.' in v and v.split('.', 1)[1] == '': raise ValueError("This float is missing digits after " "the point") if v[0] not in '0123456789': raise ValueError("This float doesn't have a leading " "digit") v = float(v) itype = "float" elif len(lowerv) == 3 and (lowerv == 'inf' or lowerv == 'nan'): v = float(v) itype = "float" if itype == "int": v = int(v, 0) if neg: return (0 - v, itype) return (v, itype) def bounded_string(self, s): if len(s) == 0: return True if s[-1] != s[0]: return False i = -2 backslash = False while len(s) + i > 0: if s[i] == "\\": backslash = not backslash i -= 1 else: break return not backslash def load_array(self, a): 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] == "'": if in_str: backslash_index = end_group_index - 1 while (backslash_index > -1 and a[backslash_index] == '\\'): in_str = not in_str backslash_index -= 1 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 (not self.bounded_string(ab) or (len(ab) > 2 and 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 = self.load_value(a[i]) if atype: if ntype != atype: raise ValueError("Not a homogeneous array") else: atype = ntype retval.append(nval) return retval toml-0.10.0/toml/encoder.py000066400000000000000000000176641335527244200155340ustar00rootroot00000000000000import datetime import re import sys from toml.decoder import InlineTableDict if sys.version_info >= (3,): unicode = str 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, encoder=None): """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 = "" if encoder is None: encoder = TomlEncoder(o.__class__) addtoretval, sections = encoder.dump_sections(o, "") retval += addtoretval while sections: newsections = encoder.get_empty_table() for section in sections: addtoretval, addtosections = encoder.dump_sections( sections[section], section) 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_str(v): if sys.version_info < (3,) and hasattr(v, 'decode') and isinstance(v, str): v = v.decode('utf-8') v = "%r" % v if v[0] == 'u': v = v[1:] singlequote = v.startswith("'") if singlequote or v.startswith('"'): v = v[1:-1] if singlequote: v = v.replace("\\'", "'") v = v.replace('"', '\\"') v = v.split("\\x") while len(v) > 1: i = -1 if not v[0]: v = v[1:] v[0] = v[0].replace("\\\\", "\\") # No, I don't know why != works and == breaks joinx = v[0][i] != "\\" while v[0][:i] and v[0][i] == "\\": joinx = not joinx i -= 1 if joinx: joiner = "x" else: joiner = "u00" v = [v[0] + joiner + v[1]] + v[2:] return unicode('"' + v[0] + '"') def _dump_float(v): return "{0:.16}".format(v).replace("e+0", "e+").replace("e-0", "e-") def _dump_time(v): utcoffset = v.utcoffset() if utcoffset is None: return v.isoformat() # The TOML norm specifies that it's local time thus we drop the offset return v.isoformat()[:-6] class TomlEncoder(object): def __init__(self, _dict=dict, preserve=False): self._dict = _dict self.preserve = preserve self.dump_funcs = { str: _dump_str, unicode: _dump_str, list: self.dump_list, bool: lambda v: unicode(v).lower(), int: lambda v: v, float: _dump_float, datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'), datetime.time: _dump_time, datetime.date: lambda v: v.isoformat() } def get_empty_table(self): return self._dict() def dump_list(self, v): retval = "[" for u in v: retval += " " + unicode(self.dump_value(u)) + "," retval += "]" return retval def dump_inline_table(self, 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 = self.dump_inline_table(v) val_list.append(k + " = " + val) retval += "{ " + ", ".join(val_list) + " }\n" return retval else: return unicode(self.dump_value(section)) def dump_value(self, v): # Lookup function corresponding to v's type dump_fn = self.dump_funcs.get(type(v)) if dump_fn is None and hasattr(v, '__iter__'): dump_fn = self.dump_funcs[list] # Evaluate function (if it exists) else return v return dump_fn(v) if dump_fn is not None else self.dump_funcs[str](v) def dump_sections(self, o, sup): retstr = "" if sup != "" and sup[-1] != ".": sup += '.' retdict = self._dict() arraystr = "" for section in o: section = unicode(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 = self.dump_sections(a, sup + qsection) if s: if s[0] == "[": arraytabstr += s else: arraystr += s while d: newd = self._dict() for dsec in d: s1, d1 = self.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 + " = " + unicode(self.dump_value(o[section])) + '\n') elif self.preserve and isinstance(o[section], InlineTableDict): retstr += (qsection + " = " + self.dump_inline_table(o[section])) else: retdict[qsection] = o[section] retstr += arraystr return (retstr, retdict) class TomlPreserveInlineDictEncoder(TomlEncoder): def __init__(self, _dict=dict): super(TomlPreserveInlineDictEncoder, self).__init__(_dict, True) class TomlArraySeparatorEncoder(TomlEncoder): def __init__(self, _dict=dict, preserve=False, separator=","): super(TomlArraySeparatorEncoder, self).__init__(_dict, preserve) if separator.strip() == "": separator = "," + separator elif separator.strip(' \t\n\r,'): raise ValueError("Invalid separator for arrays") self.separator = separator def dump_list(self, v): t = [] retval = "[" for u in v: t.append(self.dump_value(u)) while t != []: s = [] for u in t: if isinstance(u, list): for r in u: s.append(r) else: retval += " " + unicode(u) + self.separator t = s retval += "]" return retval toml-0.10.0/toml/ordered.py000066400000000000000000000005421335527244200155240ustar00rootroot00000000000000from collections import OrderedDict from toml import TomlEncoder from toml import TomlDecoder class TomlOrderedDecoder(TomlDecoder): def __init__(self): super(self.__class__, self).__init__(_dict=OrderedDict) class TomlOrderedEncoder(TomlEncoder): def __init__(self): super(self.__class__, self).__init__(_dict=OrderedDict) toml-0.10.0/toml/tz.py000066400000000000000000000011521335527244200145330ustar00rootroot00000000000000from datetime import tzinfo, timedelta class TomlTz(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 * timedelta(hours=self._hours, minutes=self._minutes) def dst(self, dt): return timedelta(0) toml-0.10.0/tox.ini000066400000000000000000000005261335527244200140700ustar00rootroot00000000000000[tox] envlist = py27, py33, py34, py35, py36, pypy [testenv] deps = pytest pytest-cov commands=pytest tests {posargs} [testenv:check] skip_install = true deps = flake8 commands = flake8 [testenv:codecov] passenv = CI TRAVIS TRAVIS_* deps = codecov pytest pytest-cov commands = pytest tests --cov=./toml codecov