├── tests ├── __init__.py ├── toml-spec-tests │ ├── errors │ │ ├── bare-key-3.toml │ │ ├── bare-key-1.toml │ │ ├── int-0-padded.toml │ │ ├── int-signed-bin.toml │ │ ├── int-signed-hex.toml │ │ ├── int-signed-oct.toml │ │ ├── bare-key-2.toml │ │ ├── comment-control-1.toml │ │ ├── comment-control-2.toml │ │ ├── comment-control-3.toml │ │ ├── comment-control-4.toml │ │ ├── key-value-pair-1.toml │ │ ├── string-basic-control-1.toml │ │ ├── string-basic-control-4.toml │ │ ├── no-key-name.toml │ │ ├── string-basic-control-2.toml │ │ ├── string-basic-control-3.toml │ │ ├── string-basic-unknown-escape.toml │ │ ├── string-literal-control-1.toml │ │ ├── string-literal-control-2.toml │ │ ├── string-literal-control-3.toml │ │ ├── string-literal-control-4.toml │ │ ├── inline-table-trailing-comma.toml │ │ ├── string-basic-multiline-control-1.toml │ │ ├── string-basic-multiline-control-2.toml │ │ ├── string-basic-multiline-control-3.toml │ │ ├── string-basic-multiline-control-4.toml │ │ ├── string-basic-multiline-unknown-escape.toml │ │ ├── string-literal-multiline-control-1.toml │ │ ├── string-literal-multiline-control-2.toml │ │ ├── string-literal-multiline-control-3.toml │ │ ├── string-literal-multiline-control-4.toml │ │ ├── string-basic-out-of-range-unicode-escape-1.toml │ │ ├── string-basic-out-of-range-unicode-escape-2.toml │ │ ├── key-value-pair-2.toml │ │ ├── multiple-key.toml │ │ ├── string-basic-multiline-out-of-range-unicode-escape-1.toml │ │ ├── string-basic-multiline-out-of-range-unicode-escape-2.toml │ │ ├── string-basic-multiline-invalid-backslash.toml │ │ ├── string-basic-multiline-quotes.toml │ │ ├── array-of-tables-1.toml │ │ ├── table-invalid-2.toml │ │ ├── inline-table-imutable-1.toml │ │ ├── inline-table-imutable-2.toml │ │ ├── table-1.toml │ │ ├── string-literal-multiline-quotes.toml │ │ ├── table-3.toml │ │ ├── table-4.toml │ │ ├── table-2.toml │ │ ├── array-of-tables-2.toml │ │ ├── multiple-dot-key.toml │ │ ├── table-invalid-1.toml │ │ ├── table-invalid-4.toml │ │ └── table-invalid-3.toml │ ├── values │ │ ├── spec-int-1.yaml │ │ ├── spec-int-2.toml │ │ ├── spec-int-2.yaml │ │ ├── spec-int-3.toml │ │ ├── spec-int-3.yaml │ │ ├── spec-int-3a.yaml │ │ ├── spec-int-3b.yaml │ │ ├── spec-int-4.yaml │ │ ├── spec-table-3.toml │ │ ├── spec-table.toml │ │ ├── spec-table.yaml │ │ ├── spec-float-1.toml │ │ ├── spec-float-1.yaml │ │ ├── spec-float-14.yaml │ │ ├── spec-float-15.yaml │ │ ├── spec-float-2.yaml │ │ ├── spec-float-3.toml │ │ ├── spec-float-3.yaml │ │ ├── spec-float-4.toml │ │ ├── spec-float-4.yaml │ │ ├── spec-float-5.toml │ │ ├── spec-float-5.yaml │ │ ├── spec-float-6.toml │ │ ├── spec-float-6.yaml │ │ ├── spec-float-9.toml │ │ ├── spec-float-9.yaml │ │ ├── spec-int-1.toml │ │ ├── spec-int-3a.toml │ │ ├── spec-int-3b.toml │ │ ├── spec-int-4.toml │ │ ├── spec-int-5.toml │ │ ├── spec-int-5.yaml │ │ ├── spec-int-bin1.yaml │ │ ├── spec-int-oct2.yaml │ │ ├── spec-key-value-pair-6.toml │ │ ├── spec-key-value-pair-6.yaml │ │ ├── spec-key-value-pair-7.toml │ │ ├── spec-key-value-pair-7.yaml │ │ ├── spec-boolean-1.toml │ │ ├── spec-boolean-1.yaml │ │ ├── spec-boolean-2.toml │ │ ├── spec-boolean-2.yaml │ │ ├── spec-comment-tab.yaml │ │ ├── spec-float-2.toml │ │ ├── spec-float-7.toml │ │ ├── spec-float-7.yaml │ │ ├── spec-int-6.toml │ │ ├── spec-int-6.yaml │ │ ├── spec-int-hex1.yaml │ │ ├── spec-int-hex2.yaml │ │ ├── spec-int-hex3.yaml │ │ ├── spec-int-oct1.yaml │ │ ├── spec-string-escape-1.toml │ │ ├── spec-string-escape-1.yaml │ │ ├── spec-string-escape-2.toml │ │ ├── spec-string-escape-2.yaml │ │ ├── spec-string-escape-3.toml │ │ ├── spec-string-escape-3.yaml │ │ ├── spec-string-escape-4.toml │ │ ├── spec-string-escape-4.yaml │ │ ├── spec-string-escape-5.toml │ │ ├── spec-string-escape-5.yaml │ │ ├── spec-string-escape-6.toml │ │ ├── spec-string-escape-6.yaml │ │ ├── spec-string-escape-7.toml │ │ ├── spec-string-escape-7.yaml │ │ ├── spec-string-escape-8.yaml │ │ ├── spec-string-escape-9.yaml │ │ ├── spec-time-1.toml │ │ ├── spec-array-1.toml │ │ ├── spec-array-1.yaml │ │ ├── spec-date-local-1.toml │ │ ├── spec-dotted-keys-2.toml │ │ ├── spec-dotted-keys-2.yaml │ │ ├── spec-dotted-keys-3.toml │ │ ├── spec-dotted-keys-3.yaml │ │ ├── spec-empty-key-name-1.yaml │ │ ├── spec-empty-key-name-2.yaml │ │ ├── spec-float-8.yaml │ │ ├── spec-int-bin1.toml │ │ ├── spec-int-hex1.toml │ │ ├── spec-int-hex2.toml │ │ ├── spec-int-hex3.toml │ │ ├── spec-int-oct1.toml │ │ ├── spec-key-value-pair-1.toml │ │ ├── spec-key-value-pair-1.yaml │ │ ├── spec-key-value-pair-5.toml │ │ ├── spec-key-value-pair-8.toml │ │ ├── spec-key-value-pair-8.yaml │ │ ├── spec-string-escape-8.toml │ │ ├── spec-time-2.toml │ │ ├── spec-float-14.toml │ │ ├── spec-float-8.toml │ │ ├── spec-key-value-pair-2.toml │ │ ├── spec-key-value-pair-2.yaml │ │ ├── spec-key-value-pair-3.toml │ │ ├── spec-key-value-pair-3.yaml │ │ ├── spec-key-value-pair-4.toml │ │ ├── spec-key-value-pair-4.yaml │ │ ├── spec-key-value-pair-5.yaml │ │ ├── spec-key-value-pair-9.toml │ │ ├── spec-newline-1.toml │ │ ├── spec-newline-1.yaml │ │ ├── spec-newline-2.toml │ │ ├── spec-newline-2.yaml │ │ ├── spec-quoted-basic-keys-1.toml │ │ ├── spec-quoted-basic-keys-1.yaml │ │ ├── spec-string-escape-9.toml │ │ ├── spec-table-3.yaml │ │ ├── spec-table-4.yaml │ │ ├── spec-table-5.yaml │ │ ├── spec-table-6.yaml │ │ ├── spec-array-7.yaml │ │ ├── spec-case-sensitive.yaml │ │ ├── spec-date-time-1.toml │ │ ├── spec-date-time-1.yaml │ │ ├── spec-date-time-4.toml │ │ ├── spec-date-time-4.yaml │ │ ├── spec-float-10.toml │ │ ├── spec-float-10.yaml │ │ ├── spec-float-11.toml │ │ ├── spec-float-11.yaml │ │ ├── spec-float-12.toml │ │ ├── spec-float-12.yaml │ │ ├── spec-int-max.toml │ │ ├── spec-int-min.toml │ │ ├── spec-key-value-pair-9.yaml │ │ ├── spec-string-basic-multiline-4.yaml │ │ ├── spec-string-basic-tab.toml │ │ ├── spec-string-basic-tab.yaml │ │ ├── spec-string-literal-4.toml │ │ ├── spec-string-literal-4.yaml │ │ ├── spec-table-inline-2.toml │ │ ├── spec-table-inline-2.yaml │ │ ├── spec-array-2.toml │ │ ├── spec-array-2.yaml │ │ ├── spec-array-7.toml │ │ ├── spec-date-time-2.toml │ │ ├── spec-date-time-2.yaml │ │ ├── spec-date-time-5.toml │ │ ├── spec-date-time-5.yaml │ │ ├── spec-date-time-6.toml │ │ ├── spec-date-time-6.yaml │ │ ├── spec-date-time-local-1.toml │ │ ├── spec-newline-3.toml │ │ ├── spec-newline-3.yaml │ │ ├── spec-table-4.toml │ │ ├── spec-table-5.toml │ │ ├── spec-date-time-3.yaml │ │ ├── spec-int-7.toml │ │ ├── spec-int-7.yaml │ │ ├── spec-quoted-literal-keys-1.toml │ │ ├── spec-quoted-literal-keys-1.yaml │ │ ├── spec-string-basic-multiline-4.toml │ │ ├── spec-string-basic-tab-multiline.yaml │ │ ├── spec-table-2.toml │ │ ├── spec-table-6.toml │ │ ├── spec-table-7.yaml │ │ ├── spec-table-inline-3.toml │ │ ├── spec-array-3.toml │ │ ├── spec-array-3.yaml │ │ ├── spec-comment-mid-string.toml │ │ ├── spec-comment-mid-string.yaml │ │ ├── spec-date-time-3.toml │ │ ├── spec-date-time-local-2.toml │ │ ├── spec-string-basic-tab-multiline.toml │ │ ├── spec-string-literal-1.toml │ │ ├── spec-string-literal-1.yaml │ │ ├── spec-string-literal-2.yaml │ │ ├── spec-string-literal-3.toml │ │ ├── spec-string-literal-3.yaml │ │ ├── spec-table-inline-3.yaml │ │ ├── spec-array-5.toml │ │ ├── spec-array-5.yaml │ │ ├── spec-array-8.toml │ │ ├── spec-array-8.yaml │ │ ├── spec-array-mixed-number-types.yaml │ │ ├── spec-empty-key-name-1.toml │ │ ├── spec-empty-key-name-2.toml │ │ ├── spec-int-oct2.toml │ │ ├── spec-string-literal-2.toml │ │ ├── spec-array-mixed-number-types.toml │ │ ├── spec-string-basic-multiline-1.yaml │ │ ├── spec-table-inline-1.toml │ │ ├── spec-table-inline-1.yaml │ │ ├── spec-array-4.yaml │ │ ├── spec-case-sensitive.toml │ │ ├── spec-string-basic-multiline-5.yaml │ │ ├── spec-string-basic-multiline-7.yaml │ │ ├── spec-string-literal-multiline-1.toml │ │ ├── spec-string-literal-multiline-1.yaml │ │ ├── spec-table-2.yaml │ │ ├── spec-array-4.toml │ │ ├── spec-float-13.toml │ │ ├── spec-float-13.yaml │ │ ├── spec-float-15.toml │ │ ├── spec-string-basic-multiline-7.toml │ │ ├── spec-string-literal-multiline-4.yaml │ │ ├── spec-extend-dotted-object-1.yaml │ │ ├── spec-string-basic-multiline-1.toml │ │ ├── spec-string-basic-multiline-2.yaml │ │ ├── spec-string-basic-multiline-3.yaml │ │ ├── spec-string-literal-multiline-4.toml │ │ ├── spec-int-max.json │ │ ├── spec-int-min.json │ │ ├── spec-string-basic-multiline-6.yaml │ │ ├── spec-string-basic-multiline-8.yaml │ │ ├── spec-string-basic-multiline-9.yaml │ │ ├── spec-string-basic-multiline-6.toml │ │ ├── spec-string-basic-multiline-9.toml │ │ ├── spec-string-basic.toml │ │ ├── spec-string-basic.yaml │ │ ├── spec-string-literal-multiline-3.yaml │ │ ├── spec-comment.yaml │ │ ├── spec-string-basic-multiline-8.toml │ │ ├── spec-string-literal-multiline-3.toml │ │ ├── spec-time-1.json │ │ ├── spec-time-2.json │ │ ├── spec-comment.toml │ │ ├── spec-date-local-1.json │ │ ├── spec-string-basic-multiline-5.toml │ │ ├── spec-string-basic-multiline-2.toml │ │ ├── spec-dotted-keys-1.toml │ │ ├── spec-dotted-keys-1.yaml │ │ ├── spec-table-1.toml │ │ ├── spec-table-1.yaml │ │ ├── spec-array-of-tables-3.yaml │ │ ├── spec-table-8.yaml │ │ ├── spec-date-time-local-1.json │ │ ├── spec-date-time-local-2.json │ │ ├── spec-string-basic-multiline-3.toml │ │ ├── spec-string-literal-multiline-2.yaml │ │ ├── spec-array-of-tables-3.toml │ │ ├── spec-array-of-tables-1.yaml │ │ ├── spec-table-7.toml │ │ ├── spec-table-8.toml │ │ ├── spec-comment-tab.toml │ │ ├── spec-string-literal-multiline-2.toml │ │ ├── spec-comment-mid-array.toml │ │ ├── spec-comment-mid-array.yaml │ │ ├── spec-array-more-mixed-types.toml │ │ ├── spec-array-more-mixed-types.yaml │ │ ├── spec-extend-dotted-object-1.toml │ │ ├── spec-extend-dotted-object-2.yaml │ │ ├── spec-extend-dotted-object-3.yaml │ │ ├── spec-array-of-tables-1.toml │ │ ├── spec-extend-dotted-object-3.toml │ │ ├── spec-extend-dotted-object-2.toml │ │ ├── spec-array-of-tables-2.toml │ │ ├── spec-array-of-tables-2.yaml │ │ ├── spec-readme-example.yaml │ │ ├── spec-readme-example.toml │ │ ├── qa-array-inline-nested-1000.toml │ │ ├── qa-array-inline-nested-1000.yaml │ │ └── qa-table-inline-nested-1000.toml │ ├── .gitattributes │ ├── README.md │ └── LICENSE ├── examples │ ├── invalid │ │ ├── array_no_comma.toml │ │ ├── invalid_date.toml │ │ ├── invalid_number.toml │ │ ├── array_leading_comma.toml │ │ ├── invalid_time.toml │ │ ├── array_duplicate_comma.toml │ │ ├── inline_table_no_comma.toml │ │ ├── trailing_comma.toml │ │ ├── inline_table_leading_comma.toml │ │ ├── inline_table_trailing_comma.toml │ │ ├── invalid_datetime.toml │ │ ├── inline_table_duplicate_comma.toml │ │ ├── newline_in_singleline_string.toml │ │ ├── string_slash_whitespace_char.toml │ │ ├── section_with_trailing_characters.toml │ │ ├── array_with_invalid_chars.toml │ │ └── key_value_with_trailing_chars.toml │ ├── preserve_quotes_in_string.toml │ ├── newline_in_strings.toml │ ├── string_slash_whitespace_newline.toml │ ├── sections_with_same_start.toml │ ├── table_names.toml │ ├── fruit.toml │ ├── json │ │ ├── table_names.json │ │ ├── 0.5.0.json │ │ └── pyproject.json │ ├── 0.5.0.toml │ ├── example.toml │ ├── hard.toml │ ├── test.toml │ └── pyproject.toml ├── test_write.py ├── test_parser.py ├── util.py ├── test_utils.py ├── test_toml_tests.py ├── conftest.py ├── test_toml_file.py ├── test_build.py ├── test_toml_spec_tests.py └── test_api.py ├── tomlkit ├── py.typed ├── toml_document.py ├── _compat.py ├── toml_char.py ├── __init__.py ├── toml_file.py ├── _types.py ├── _utils.py ├── source.py ├── exceptions.py └── api.py ├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ ├── tests.yml │ └── integration.yml ├── docs ├── requirements.in ├── Makefile ├── api.rst ├── make.bat ├── requirements.txt ├── index.rst ├── conf.py └── quickstart.rst ├── .coveragerc ├── tox.ini ├── .gitmodules ├── .gitignore ├── .readthedocs.yaml ├── .pre-commit-config.yaml ├── LICENSE ├── pyproject.toml └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tomlkit/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [sdispater] 2 | -------------------------------------------------------------------------------- /docs/requirements.in: -------------------------------------------------------------------------------- 1 | sphinx 2 | furo 3 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = tomlkit/_compat.py, 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/bare-key-3.toml: -------------------------------------------------------------------------------- 1 | barekey = -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-1.yaml: -------------------------------------------------------------------------------- 1 | int1: 99 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-2.toml: -------------------------------------------------------------------------------- 1 | int2 = 42 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-2.yaml: -------------------------------------------------------------------------------- 1 | int2: 42 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-3.toml: -------------------------------------------------------------------------------- 1 | int3 = 0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-3.yaml: -------------------------------------------------------------------------------- 1 | int3: 0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-3a.yaml: -------------------------------------------------------------------------------- 1 | int3: 0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-3b.yaml: -------------------------------------------------------------------------------- 1 | int3: 0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-4.yaml: -------------------------------------------------------------------------------- 1 | int4: -17 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-3.toml: -------------------------------------------------------------------------------- 1 | [a.b.c] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table.toml: -------------------------------------------------------------------------------- 1 | [table] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table.yaml: -------------------------------------------------------------------------------- 1 | table: {} 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/array_no_comma.toml: -------------------------------------------------------------------------------- 1 | simple = [ 1 2 ] 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/invalid_date.toml: -------------------------------------------------------------------------------- 1 | date = 2018-13-33 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/invalid_number.toml: -------------------------------------------------------------------------------- 1 | number = 34"5" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/bare-key-1.toml: -------------------------------------------------------------------------------- 1 | bare!key = 123 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/int-0-padded.toml: -------------------------------------------------------------------------------- 1 | int = 0123 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-1.toml: -------------------------------------------------------------------------------- 1 | flt1 = +1.0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-1.yaml: -------------------------------------------------------------------------------- 1 | flt1: 1.0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-14.yaml: -------------------------------------------------------------------------------- 1 | sf5: .nan 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-15.yaml: -------------------------------------------------------------------------------- 1 | sf6: .NAN 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-2.yaml: -------------------------------------------------------------------------------- 1 | flt2: 3.1415 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-3.toml: -------------------------------------------------------------------------------- 1 | flt3 = -0.01 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-3.yaml: -------------------------------------------------------------------------------- 1 | flt3: -0.01 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-4.toml: -------------------------------------------------------------------------------- 1 | flt4 = 5e+22 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-4.yaml: -------------------------------------------------------------------------------- 1 | flt4: 5e+22 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-5.toml: -------------------------------------------------------------------------------- 1 | flt5 = 1e06 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-5.yaml: -------------------------------------------------------------------------------- 1 | flt5: 1e6 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-6.toml: -------------------------------------------------------------------------------- 1 | flt6 = -2E-2 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-6.yaml: -------------------------------------------------------------------------------- 1 | flt6: -2E-2 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-9.toml: -------------------------------------------------------------------------------- 1 | flt9 = -0e0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-9.yaml: -------------------------------------------------------------------------------- 1 | flt9: -0e0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-1.toml: -------------------------------------------------------------------------------- 1 | int1 = +99 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-3a.toml: -------------------------------------------------------------------------------- 1 | int3 = +0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-3b.toml: -------------------------------------------------------------------------------- 1 | int3 = -0 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-4.toml: -------------------------------------------------------------------------------- 1 | int4 = -17 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-5.toml: -------------------------------------------------------------------------------- 1 | int5 = 1_000 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-5.yaml: -------------------------------------------------------------------------------- 1 | int5: 1_000 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-bin1.yaml: -------------------------------------------------------------------------------- 1 | bin1: 214 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-oct2.yaml: -------------------------------------------------------------------------------- 1 | oct2: 493 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-6.toml: -------------------------------------------------------------------------------- 1 | -=1 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-6.yaml: -------------------------------------------------------------------------------- 1 | "-": 1 -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-7.toml: -------------------------------------------------------------------------------- 1 | _=1 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-7.yaml: -------------------------------------------------------------------------------- 1 | "_": 1 -------------------------------------------------------------------------------- /tests/examples/invalid/array_leading_comma.toml: -------------------------------------------------------------------------------- 1 | simple = [ , 1 ] 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/invalid_time.toml: -------------------------------------------------------------------------------- 1 | datetime = 26:61:61 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/int-signed-bin.toml: -------------------------------------------------------------------------------- 1 | bin = +0b10 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/int-signed-hex.toml: -------------------------------------------------------------------------------- 1 | hex = +0xab 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/int-signed-oct.toml: -------------------------------------------------------------------------------- 1 | oct = +0o23 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-boolean-1.toml: -------------------------------------------------------------------------------- 1 | bool1 = true 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-boolean-1.yaml: -------------------------------------------------------------------------------- 1 | bool1: true 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-boolean-2.toml: -------------------------------------------------------------------------------- 1 | bool1 = false 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-boolean-2.yaml: -------------------------------------------------------------------------------- 1 | bool1: false 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-comment-tab.yaml: -------------------------------------------------------------------------------- 1 | key: "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-2.toml: -------------------------------------------------------------------------------- 1 | flt2 = 3.1415 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-7.toml: -------------------------------------------------------------------------------- 1 | flt7 = 6.626e-34 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-7.yaml: -------------------------------------------------------------------------------- 1 | flt7: 6.626e-34 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-6.toml: -------------------------------------------------------------------------------- 1 | int6 = 5_349_221 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-6.yaml: -------------------------------------------------------------------------------- 1 | int6: 5_349_221 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-hex1.yaml: -------------------------------------------------------------------------------- 1 | hex1: 3735928559 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-hex2.yaml: -------------------------------------------------------------------------------- 1 | hex2: 3735928559 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-hex3.yaml: -------------------------------------------------------------------------------- 1 | hex3: 3735928559 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-oct1.yaml: -------------------------------------------------------------------------------- 1 | oct1: 342391 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-1.toml: -------------------------------------------------------------------------------- 1 | a = "\b" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-1.yaml: -------------------------------------------------------------------------------- 1 | a: "\b" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-2.toml: -------------------------------------------------------------------------------- 1 | a = "\t" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-2.yaml: -------------------------------------------------------------------------------- 1 | a: "\t" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-3.toml: -------------------------------------------------------------------------------- 1 | a = "\n" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-3.yaml: -------------------------------------------------------------------------------- 1 | a: "\n" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-4.toml: -------------------------------------------------------------------------------- 1 | a = "\f" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-4.yaml: -------------------------------------------------------------------------------- 1 | a: "\f" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-5.toml: -------------------------------------------------------------------------------- 1 | a = "\r" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-5.yaml: -------------------------------------------------------------------------------- 1 | a: "\r" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-6.toml: -------------------------------------------------------------------------------- 1 | a = "\"" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-6.yaml: -------------------------------------------------------------------------------- 1 | a: "\"" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-7.toml: -------------------------------------------------------------------------------- 1 | a = "\\" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-7.yaml: -------------------------------------------------------------------------------- 1 | a: "\\" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-8.yaml: -------------------------------------------------------------------------------- 1 | a: "\x00" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-9.yaml: -------------------------------------------------------------------------------- 1 | a: "\x00" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-time-1.toml: -------------------------------------------------------------------------------- 1 | lt1 = 07:32:00 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/array_duplicate_comma.toml: -------------------------------------------------------------------------------- 1 | simple = [ 1 ,, 2 ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/bare-key-2.toml: -------------------------------------------------------------------------------- 1 | barekey 2 | = 123 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/comment-control-1.toml: -------------------------------------------------------------------------------- 1 | a = "null" # 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/comment-control-2.toml: -------------------------------------------------------------------------------- 1 | a = "ctrl-P" #  2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/comment-control-3.toml: -------------------------------------------------------------------------------- 1 | a = "ctrl-_" #  2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/comment-control-4.toml: -------------------------------------------------------------------------------- 1 | a = "0x7f" #  2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/key-value-pair-1.toml: -------------------------------------------------------------------------------- 1 | key = # INVALID 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-control-1.toml: -------------------------------------------------------------------------------- 1 | a = "null" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-control-4.toml: -------------------------------------------------------------------------------- 1 | a = "0x7f" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-1.toml: -------------------------------------------------------------------------------- 1 | integers = [ 1, 2, 3 ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-1.yaml: -------------------------------------------------------------------------------- 1 | integers: [ 1, 2, 3 ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-local-1.toml: -------------------------------------------------------------------------------- 1 | ld1 = 1979-05-27 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-dotted-keys-2.toml: -------------------------------------------------------------------------------- 1 | a . b = 23 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-dotted-keys-2.yaml: -------------------------------------------------------------------------------- 1 | a: 2 | b: 23 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-dotted-keys-3.toml: -------------------------------------------------------------------------------- 1 | a . b = 23 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-dotted-keys-3.yaml: -------------------------------------------------------------------------------- 1 | a: 2 | b: 23 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-empty-key-name-1.yaml: -------------------------------------------------------------------------------- 1 | "": "blank" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-empty-key-name-2.yaml: -------------------------------------------------------------------------------- 1 | "": "blank" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-8.yaml: -------------------------------------------------------------------------------- 1 | flt8: 224_617.445_991_228 -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-bin1.toml: -------------------------------------------------------------------------------- 1 | bin1 = 0b11010110 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-hex1.toml: -------------------------------------------------------------------------------- 1 | hex1 = 0xDEADBEEF 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-hex2.toml: -------------------------------------------------------------------------------- 1 | hex2 = 0xdeadbeef 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-hex3.toml: -------------------------------------------------------------------------------- 1 | hex3 = 0xdead_beef 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-oct1.toml: -------------------------------------------------------------------------------- 1 | oct1 = 0o01234567 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-1.toml: -------------------------------------------------------------------------------- 1 | key = "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-1.yaml: -------------------------------------------------------------------------------- 1 | key: "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-5.toml: -------------------------------------------------------------------------------- 1 | 1234="value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-8.toml: -------------------------------------------------------------------------------- 1 | -_-_-_-_-=1 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-8.yaml: -------------------------------------------------------------------------------- 1 | "-_-_-_-_-": 1 -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-8.toml: -------------------------------------------------------------------------------- 1 | a = "\u0000" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-time-2.toml: -------------------------------------------------------------------------------- 1 | lt2 = 00:32:00.999999 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/inline_table_no_comma.toml: -------------------------------------------------------------------------------- 1 | simple = { a = 1 b = 2} 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/trailing_comma.toml: -------------------------------------------------------------------------------- 1 | hello = "world" , # this 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/no-key-name.toml: -------------------------------------------------------------------------------- 1 | = "no key name" # INVALID 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-control-2.toml: -------------------------------------------------------------------------------- 1 | a = "ctrl-P" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-control-3.toml: -------------------------------------------------------------------------------- 1 | a = "ctrl-_" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-unknown-escape.toml: -------------------------------------------------------------------------------- 1 | a = "\@" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-control-1.toml: -------------------------------------------------------------------------------- 1 | a = 'null' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-control-2.toml: -------------------------------------------------------------------------------- 1 | a = 'null' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-control-3.toml: -------------------------------------------------------------------------------- 1 | a = 'null' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-control-4.toml: -------------------------------------------------------------------------------- 1 | a = 'null' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-14.toml: -------------------------------------------------------------------------------- 1 | sf5 = +nan # same as `nan` 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-8.toml: -------------------------------------------------------------------------------- 1 | flt8 = 224_617.445_991_228 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-2.toml: -------------------------------------------------------------------------------- 1 | bare_key = "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-2.yaml: -------------------------------------------------------------------------------- 1 | bare_key: "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-3.toml: -------------------------------------------------------------------------------- 1 | bare-key = "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-3.yaml: -------------------------------------------------------------------------------- 1 | bare-key: "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-4.toml: -------------------------------------------------------------------------------- 1 | 1234 = "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-4.yaml: -------------------------------------------------------------------------------- 1 | "1234": "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-5.yaml: -------------------------------------------------------------------------------- 1 | "1234": "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-9.toml: -------------------------------------------------------------------------------- 1 | 3.14159 = "pi" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-newline-1.toml: -------------------------------------------------------------------------------- 1 | abc = 123 2 | def = 456 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-newline-1.yaml: -------------------------------------------------------------------------------- 1 | abc: 123 2 | def: 456 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-newline-2.toml: -------------------------------------------------------------------------------- 1 | abc = 123 2 | def = 456 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-newline-2.yaml: -------------------------------------------------------------------------------- 1 | abc: 123 2 | def: 456 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-quoted-basic-keys-1.toml: -------------------------------------------------------------------------------- 1 | "ʎǝʞ" = "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-quoted-basic-keys-1.yaml: -------------------------------------------------------------------------------- 1 | "ʎǝʞ": "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-escape-9.toml: -------------------------------------------------------------------------------- 1 | a = "\U00000000" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-3.yaml: -------------------------------------------------------------------------------- 1 | a: 2 | b: 3 | c: {} 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-4.yaml: -------------------------------------------------------------------------------- 1 | d: 2 | e: 3 | f: {} 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-5.yaml: -------------------------------------------------------------------------------- 1 | g: 2 | h: 3 | i: {} 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-6.yaml: -------------------------------------------------------------------------------- 1 | j: 2 | "ʞ": 3 | l: {} 4 | -------------------------------------------------------------------------------- /tests/examples/invalid/inline_table_leading_comma.toml: -------------------------------------------------------------------------------- 1 | simple = { , a = 1 } 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/inline_table_trailing_comma.toml: -------------------------------------------------------------------------------- 1 | simple = { a = 1 , } 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/invalid_datetime.toml: -------------------------------------------------------------------------------- 1 | datetime = 2018-13-33T23:12:34 2 | -------------------------------------------------------------------------------- /tests/examples/preserve_quotes_in_string.toml: -------------------------------------------------------------------------------- 1 | this = """this 2 | "" 3 | more""" 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-7.yaml: -------------------------------------------------------------------------------- 1 | integers2: [ 2 | 1, 2, 3 3 | ] 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-case-sensitive.yaml: -------------------------------------------------------------------------------- 1 | abc: 123 2 | ABC: 456 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-1.toml: -------------------------------------------------------------------------------- 1 | odt1 = 1979-05-27T07:32:00Z 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-1.yaml: -------------------------------------------------------------------------------- 1 | odt1: 1979-05-27T07:32:00Z 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-4.toml: -------------------------------------------------------------------------------- 1 | odt4 = 1979-05-27 07:32:00Z 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-4.yaml: -------------------------------------------------------------------------------- 1 | odt4: 1979-05-27 07:32:00Z 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-10.toml: -------------------------------------------------------------------------------- 1 | sf1 = inf # positive infinity 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-10.yaml: -------------------------------------------------------------------------------- 1 | sf1: .inf # positive infinity 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-11.toml: -------------------------------------------------------------------------------- 1 | sf2 = +inf # positive infinity 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-11.yaml: -------------------------------------------------------------------------------- 1 | sf2: .inf # positive infinity 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-12.toml: -------------------------------------------------------------------------------- 1 | sf2 = -inf # negative infinity 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-12.yaml: -------------------------------------------------------------------------------- 1 | sf2: -.inf # negative infinity 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-max.toml: -------------------------------------------------------------------------------- 1 | max=9_223_372_036_854_775_807 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-min.toml: -------------------------------------------------------------------------------- 1 | min=-9_223_372_036_854_775_808 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-key-value-pair-9.yaml: -------------------------------------------------------------------------------- 1 | 3: 2 | 14159: "pi" 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-4.yaml: -------------------------------------------------------------------------------- 1 | a: "abcdef" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-tab.toml: -------------------------------------------------------------------------------- 1 | str = "This is a tab" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-tab.yaml: -------------------------------------------------------------------------------- 1 | str: "This is a\ttab" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-4.toml: -------------------------------------------------------------------------------- 1 | regex = '<\i\c*\s*>' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-4.yaml: -------------------------------------------------------------------------------- 1 | regex: '<\i\c*\s*>' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-inline-2.toml: -------------------------------------------------------------------------------- 1 | point = { x = 1, y = 2 } 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-inline-2.yaml: -------------------------------------------------------------------------------- 1 | point: { x: 1, y: 2 } 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/inline_table_duplicate_comma.toml: -------------------------------------------------------------------------------- 1 | simple = { a = 1 ,, b = 2} 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/inline-table-trailing-comma.toml: -------------------------------------------------------------------------------- 1 | abc = { abc = 123, } 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-control-1.toml: -------------------------------------------------------------------------------- 1 | a = """null""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-control-2.toml: -------------------------------------------------------------------------------- 1 | a = """null""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-control-3.toml: -------------------------------------------------------------------------------- 1 | a = """null""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-control-4.toml: -------------------------------------------------------------------------------- 1 | a = """null""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-unknown-escape.toml: -------------------------------------------------------------------------------- 1 | a = """\@""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-multiline-control-1.toml: -------------------------------------------------------------------------------- 1 | a = '''null''' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-multiline-control-2.toml: -------------------------------------------------------------------------------- 1 | a = '''null''' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-multiline-control-3.toml: -------------------------------------------------------------------------------- 1 | a = '''null''' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-multiline-control-4.toml: -------------------------------------------------------------------------------- 1 | a = '''null''' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-2.toml: -------------------------------------------------------------------------------- 1 | colors = [ "red", "yellow", "green" ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-2.yaml: -------------------------------------------------------------------------------- 1 | colors: [ "red", "yellow", "green" ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-7.toml: -------------------------------------------------------------------------------- 1 | integers2 = [ 2 | 1, 2, 3 3 | ] 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-2.toml: -------------------------------------------------------------------------------- 1 | odt2 = 1979-05-27T00:32:00-07:00 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-2.yaml: -------------------------------------------------------------------------------- 1 | odt2: 1979-05-27T00:32:00-07:00 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-5.toml: -------------------------------------------------------------------------------- 1 | odt5 = 1979-05-27T07:32:00.123Z 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-5.yaml: -------------------------------------------------------------------------------- 1 | odt5: 1979-05-27T07:32:00.123Z 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-6.toml: -------------------------------------------------------------------------------- 1 | odt6 = 1979-05-27T07:32:00.1239Z 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-6.yaml: -------------------------------------------------------------------------------- 1 | odt6: 1979-05-27T07:32:00.123Z 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-local-1.toml: -------------------------------------------------------------------------------- 1 | ldt1 = 1979-05-27T07:32:00 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-newline-3.toml: -------------------------------------------------------------------------------- 1 | abc = 123 2 | def = 456 3 | ghi = 789 -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-newline-3.yaml: -------------------------------------------------------------------------------- 1 | abc: 123 2 | def: 456 3 | ghi: 789 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-4.toml: -------------------------------------------------------------------------------- 1 | [ d.e.f ] # same as [d.e.f] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-5.toml: -------------------------------------------------------------------------------- 1 | [ g . h . i ] # same as [g.h.i] 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/newline_in_singleline_string.toml: -------------------------------------------------------------------------------- 1 | this = "this 2 | that 3 | more" 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-3.yaml: -------------------------------------------------------------------------------- 1 | odt3: 1979-05-27T00:32:00.999999-07:00 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-7.toml: -------------------------------------------------------------------------------- 1 | int7 = 1_2_3_4_5 # VALID but discouraged 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-7.yaml: -------------------------------------------------------------------------------- 1 | int7: 1_2_3_4_5 # VALID but discouraged 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-quoted-literal-keys-1.toml: -------------------------------------------------------------------------------- 1 | 'quoted "value"' = "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-quoted-literal-keys-1.yaml: -------------------------------------------------------------------------------- 1 | 'quoted "value"': "value" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-4.toml: -------------------------------------------------------------------------------- 1 | a = """abc\ 2 | def""" 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-tab-multiline.yaml: -------------------------------------------------------------------------------- 1 | str: "This is a\ttab" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-2.toml: -------------------------------------------------------------------------------- 1 | [dog."tater.man"] 2 | type.name = "pug" 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-6.toml: -------------------------------------------------------------------------------- 1 | [ j . "ʞ" . 'l' ] # same as [j."ʞ".'l'] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-7.yaml: -------------------------------------------------------------------------------- 1 | x: 2 | y: 3 | z: 4 | w: {} 5 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-inline-3.toml: -------------------------------------------------------------------------------- 1 | animal = { type.name = "pug" } 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/string_slash_whitespace_char.toml: -------------------------------------------------------------------------------- 1 | invalid_escape = """this \ more""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-out-of-range-unicode-escape-1.toml: -------------------------------------------------------------------------------- 1 | a = "\UFFFFFFFF" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-out-of-range-unicode-escape-2.toml: -------------------------------------------------------------------------------- 1 | a = "\U00D80000" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-3.toml: -------------------------------------------------------------------------------- 1 | nested_array_of_int = [ [ 1, 2 ], [3, 4, 5] ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-3.yaml: -------------------------------------------------------------------------------- 1 | nested_array_of_int: [ [ 1, 2 ], [3, 4, 5] ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-comment-mid-string.toml: -------------------------------------------------------------------------------- 1 | another = "# This is not a comment" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-comment-mid-string.yaml: -------------------------------------------------------------------------------- 1 | another: "# This is not a comment" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-3.toml: -------------------------------------------------------------------------------- 1 | odt3 = 1979-05-27T00:32:00.999999-07:00 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-local-2.toml: -------------------------------------------------------------------------------- 1 | ldt2 = 1979-05-27T00:32:00.999999 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-tab-multiline.toml: -------------------------------------------------------------------------------- 1 | str = """This is a tab""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-1.toml: -------------------------------------------------------------------------------- 1 | winpath = 'C:\Users\nodejs\templates' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-1.yaml: -------------------------------------------------------------------------------- 1 | winpath: 'C:\Users\nodejs\templates' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-2.yaml: -------------------------------------------------------------------------------- 1 | winpath2: '\\ServerX\admin$\system32\' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-3.toml: -------------------------------------------------------------------------------- 1 | quoted = 'Tom "Dubs" Preston-Werner' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-3.yaml: -------------------------------------------------------------------------------- 1 | quoted: 'Tom "Dubs" Preston-Werner' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-inline-3.yaml: -------------------------------------------------------------------------------- 1 | animal: { type: { name: "pug" } } 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/key-value-pair-2.toml: -------------------------------------------------------------------------------- 1 | first = "Tom" last = "Preston-Werner" # INVALID 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-5.toml: -------------------------------------------------------------------------------- 1 | nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-5.yaml: -------------------------------------------------------------------------------- 1 | nested_mixed_array: [ [ 1, 2 ], ["a", "b", "c"] ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-8.toml: -------------------------------------------------------------------------------- 1 | integers3 = [ 2 | 1, 3 | 2, # this is ok 4 | ] 5 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-8.yaml: -------------------------------------------------------------------------------- 1 | integers3: [ 2 | 1, 3 | 2, # this is ok 4 | ] 5 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-mixed-number-types.yaml: -------------------------------------------------------------------------------- 1 | numbers: [ 0.1, 0.2, 0.5, 1, 2, 5 ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-empty-key-name-1.toml: -------------------------------------------------------------------------------- 1 | "" = "blank" # VALID but discouraged 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-empty-key-name-2.toml: -------------------------------------------------------------------------------- 1 | '' = "blank" # VALID but discouraged 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-oct2.toml: -------------------------------------------------------------------------------- 1 | oct2 = 0o755 # useful for Unix file permissions 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-2.toml: -------------------------------------------------------------------------------- 1 | winpath2 = '\\ServerX\admin$\system32\' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/multiple-key.toml: -------------------------------------------------------------------------------- 1 | # DO NOT DO THIS 2 | name = "Tom" 3 | name = "Pradyun" 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-mixed-number-types.toml: -------------------------------------------------------------------------------- 1 | numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-1.yaml: -------------------------------------------------------------------------------- 1 | str1: "Roses are red\nViolets are blue" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-inline-1.toml: -------------------------------------------------------------------------------- 1 | name = { first = "Tom", last = "Preston-Werner" } 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-inline-1.yaml: -------------------------------------------------------------------------------- 1 | name: { first: "Tom", last: "Preston-Werner" } 2 | -------------------------------------------------------------------------------- /tests/examples/newline_in_strings.toml: -------------------------------------------------------------------------------- 1 | this = "this\\nthat\\nmore" 2 | foo = """bar 3 | baz 4 | qux 5 | """ 6 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-out-of-range-unicode-escape-1.toml: -------------------------------------------------------------------------------- 1 | a = """\UFFFFFFFF""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-out-of-range-unicode-escape-2.toml: -------------------------------------------------------------------------------- 1 | a = """\U00D80000""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-4.yaml: -------------------------------------------------------------------------------- 1 | string_array: [ "all", "strings", "are the same", "type" ] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-case-sensitive.toml: -------------------------------------------------------------------------------- 1 | # TOML is case sensitive. 2 | abc = 123 3 | ABC = 456 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-5.yaml: -------------------------------------------------------------------------------- 1 | ml-escaped-nl: " foo bar \\\n baz \\quux" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-7.yaml: -------------------------------------------------------------------------------- 1 | str5: 'Here are three quotation marks: """.' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-multiline-1.toml: -------------------------------------------------------------------------------- 1 | regex2 = '''I [dw]on't need \d{2} apples''' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-multiline-1.yaml: -------------------------------------------------------------------------------- 1 | regex2: "I [dw]on't need \\d{2} apples" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-2.yaml: -------------------------------------------------------------------------------- 1 | dog: 2 | "tater.man": 3 | type: 4 | name: "pug" 5 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/.gitattributes: -------------------------------------------------------------------------------- 1 | *.toml text=auto 2 | *.yaml text=auto 3 | values/spec-newline-*.toml binary 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-invalid-backslash.toml: -------------------------------------------------------------------------------- 1 | a = """ 2 | foo \ \n 3 | bar""" 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-basic-multiline-quotes.toml: -------------------------------------------------------------------------------- 1 | str5 = """Here are three quotation marks: """.""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-4.toml: -------------------------------------------------------------------------------- 1 | string_array = [ "all", 'strings', """are the same""", '''type'''] 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-13.toml: -------------------------------------------------------------------------------- 1 | sf4 = nan # actual sNaN/qNaN encoding is implementation specific 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-13.yaml: -------------------------------------------------------------------------------- 1 | sf4: .NAN # actual sNaN/qNaN encoding is implementation specific 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-float-15.toml: -------------------------------------------------------------------------------- 1 | sf6 = -nan # valid, actual encoding is implementation specific 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-7.toml: -------------------------------------------------------------------------------- 1 | str5 = """Here are three quotation marks: ""\".""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-multiline-4.yaml: -------------------------------------------------------------------------------- 1 | str: "'That,' she said, 'is still pointless.'" 2 | -------------------------------------------------------------------------------- /tests/examples/invalid/section_with_trailing_characters.toml: -------------------------------------------------------------------------------- 1 | [error] if you didn't catch this, your parser is broken 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/array-of-tables-1.toml: -------------------------------------------------------------------------------- 1 | # INVALID TOML DOC 2 | fruit = [] 3 | 4 | [[fruit]] # Not allowed 5 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/table-invalid-2.toml: -------------------------------------------------------------------------------- 1 | # INVALID TOML DOC 2 | fruit = [] 3 | 4 | [[fruit]] # Not allowed 5 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-extend-dotted-object-1.yaml: -------------------------------------------------------------------------------- 1 | fruit: 2 | apple: 3 | smooth: true 4 | orange: 2 5 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-1.toml: -------------------------------------------------------------------------------- 1 | str1 = """ 2 | Roses are red 3 | Violets are blue""" 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-2.yaml: -------------------------------------------------------------------------------- 1 | str: "The quick brown fox jumps over the lazy dog." 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-3.yaml: -------------------------------------------------------------------------------- 1 | str: "The quick brown fox jumps over the lazy dog." 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-multiline-4.toml: -------------------------------------------------------------------------------- 1 | str = ''''That,' she said, 'is still pointless.'''' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-max.json: -------------------------------------------------------------------------------- 1 | { 2 | "max": {"type": "integer", "value": "9223372036854775807"} 3 | } 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-int-min.json: -------------------------------------------------------------------------------- 1 | { 2 | "min": {"type": "integer", "value": "-9223372036854775808"} 3 | } 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-6.yaml: -------------------------------------------------------------------------------- 1 | str4: 'Here are two quotation marks: "". Simple enough.' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-8.yaml: -------------------------------------------------------------------------------- 1 | str6: 'Here are fifteen quotation marks: """"""""""""""".' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-9.yaml: -------------------------------------------------------------------------------- 1 | str7: '"This," she said, "is just a pointless statement."' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-6.toml: -------------------------------------------------------------------------------- 1 | str4 = """Here are two quotation marks: "". Simple enough.""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-9.toml: -------------------------------------------------------------------------------- 1 | str7 = """"This," she said, "is just a pointless statement."""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic.toml: -------------------------------------------------------------------------------- 1 | str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic.yaml: -------------------------------------------------------------------------------- 1 | str: "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-multiline-3.yaml: -------------------------------------------------------------------------------- 1 | quot15: 'Here are fifteen quotation marks: """""""""""""""' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/inline-table-imutable-1.toml: -------------------------------------------------------------------------------- 1 | [product] 2 | type = { name = "Nail" } 3 | type.edible = false # INVALID 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/inline-table-imutable-2.toml: -------------------------------------------------------------------------------- 1 | [product] 2 | type.name = "Nail" 3 | type = { edible = false } # INVALID 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/table-1.toml: -------------------------------------------------------------------------------- 1 | # DO NOT DO THIS 2 | 3 | [fruit] 4 | apple = "red" 5 | 6 | [fruit] 7 | orange = "orange" 8 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-comment.yaml: -------------------------------------------------------------------------------- 1 | # This is a full-line comment 2 | key: "value" # This is a comment at the end of a line 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-8.toml: -------------------------------------------------------------------------------- 1 | str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-multiline-3.toml: -------------------------------------------------------------------------------- 1 | quot15 = '''Here are fifteen quotation marks: """""""""""""""''' 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-time-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "lt1": { 3 | "type": "time", 4 | "value": "07:32:00.000" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-time-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "lt2": { 3 | "type": "time", 4 | "value": "00:32:00.999" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/string-literal-multiline-quotes.toml: -------------------------------------------------------------------------------- 1 | apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/table-3.toml: -------------------------------------------------------------------------------- 1 | [fruit] 2 | apple.color = "red" 3 | apple.taste.sweet = true 4 | 5 | [fruit.apple] # INVALID 6 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-comment.toml: -------------------------------------------------------------------------------- 1 | # This is a full-line comment 2 | key = "value" # This is a comment at the end of a line 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-local-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ld1": { 3 | "type": "date", 4 | "value": "1979-05-27" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-5.toml: -------------------------------------------------------------------------------- 1 | ml-escaped-nl = """ 2 | foo \ 3 | bar \\ 4 | baz \\\ 5 | quux""" 6 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/table-4.toml: -------------------------------------------------------------------------------- 1 | [fruit] 2 | apple.color = "red" 3 | apple.taste.sweet = true 4 | 5 | [fruit.apple.taste] # INVALID 6 | -------------------------------------------------------------------------------- /tests/examples/string_slash_whitespace_newline.toml: -------------------------------------------------------------------------------- 1 | no_whitespace = """hello \ 2 | world""" 3 | 4 | has_whitespace = """hello \ 5 | world""" 6 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/table-2.toml: -------------------------------------------------------------------------------- 1 | # DO NOT DO THIS EITHER 2 | 3 | [fruit] 4 | apple = "red" 5 | 6 | [fruit.apple] 7 | texture = "smooth" 8 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-2.toml: -------------------------------------------------------------------------------- 1 | str = """ 2 | The quick brown \ 3 | 4 | 5 | fox jumps over \ 6 | the lazy dog.""" 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-dotted-keys-1.toml: -------------------------------------------------------------------------------- 1 | name = "Orange" 2 | physical.color = "orange" 3 | physical.shape = "round" 4 | site."google.com" = true 5 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-dotted-keys-1.yaml: -------------------------------------------------------------------------------- 1 | name: "Orange" 2 | physical: 3 | color: "orange" 4 | shape: "round" 5 | site: 6 | "google.com": true 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-1.toml: -------------------------------------------------------------------------------- 1 | [table-1] 2 | key1 = "some string" 3 | key2 = 123 4 | 5 | [table-2] 6 | key1 = "another string" 7 | key2 = 456 8 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-1.yaml: -------------------------------------------------------------------------------- 1 | table-1: 2 | key1: "some string" 3 | key2: 123 4 | table-2: 5 | key1: "another string" 6 | key2: 456 7 | -------------------------------------------------------------------------------- /tomlkit/toml_document.py: -------------------------------------------------------------------------------- 1 | from tomlkit.container import Container 2 | 3 | 4 | class TOMLDocument(Container): 5 | """ 6 | A TOML document. 7 | """ 8 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-of-tables-3.yaml: -------------------------------------------------------------------------------- 1 | points: [ { x: 1, y: 2, z: 3 }, 2 | { x: 7, y: 8, z: 9 }, 3 | { x: 2, y: 4, z: 8 } ] 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-8.yaml: -------------------------------------------------------------------------------- 1 | fruit: 2 | apple: 3 | color: "red" 4 | taste: 5 | sweet: true 6 | texture: 7 | smooth: true 8 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-local-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ldt1": { 3 | "type": "datetime-local", 4 | "value": "1979-05-27T07:32:00.000" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-date-time-local-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ldt2": { 3 | "type": "datetime-local", 4 | "value": "1979-05-27T00:32:00.999" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-basic-multiline-3.toml: -------------------------------------------------------------------------------- 1 | str = """\ 2 | The quick brown \ 3 | fox jumps over \ 4 | the lazy dog.\ 5 | """ 6 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-multiline-2.yaml: -------------------------------------------------------------------------------- 1 | lines: "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n" 2 | -------------------------------------------------------------------------------- /tests/examples/sections_with_same_start.toml: -------------------------------------------------------------------------------- 1 | [section] 2 | key = "value" 3 | 4 | [section2] 5 | "key2" = 12.34 6 | 7 | [section2.sub-section] 8 | "key3" = "something" 9 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-of-tables-3.toml: -------------------------------------------------------------------------------- 1 | points = [ { x = 1, y = 2, z = 3 }, 2 | { x = 7, y = 8, z = 9 }, 3 | { x = 2, y = 4, z = 8 } ] 4 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-of-tables-1.yaml: -------------------------------------------------------------------------------- 1 | products: [ 2 | {name: "Hammer", sku: 738594937}, 3 | {}, 4 | {name: "Nail", sku: 284758393, color: "gray" } 5 | ] 6 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-7.toml: -------------------------------------------------------------------------------- 1 | # [x] you 2 | # [x.y] don't 3 | # [x.y.z] need these 4 | [x.y.z.w] # for this to work 5 | [x] # defining a super-table afterwards is ok 6 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-table-8.toml: -------------------------------------------------------------------------------- 1 | [fruit] 2 | apple.color = "red" 3 | apple.taste.sweet = true 4 | 5 | [fruit.apple.texture] # you can add sub-tables 6 | smooth = true 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-comment-tab.toml: -------------------------------------------------------------------------------- 1 | # This is a full-line comment with a tab in the middle 2 | key = "value" # This is a commen with a tab in the middle at the end of a line 3 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-string-literal-multiline-2.toml: -------------------------------------------------------------------------------- 1 | lines = ''' 2 | The first newline is 3 | trimmed in raw strings. 4 | All other whitespace 5 | is preserved. 6 | ''' 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-comment-mid-array.toml: -------------------------------------------------------------------------------- 1 | # eol commetns can go anywhere 2 | abc = [ # this is valid 3 | 123,#as is this 4 | 456 #so is this 5 | ]# and this 6 | # here too 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-comment-mid-array.yaml: -------------------------------------------------------------------------------- 1 | # eol commetns can go anywhere 2 | abc: [ # this is valid 3 | 123,#as is this 4 | 456 #so is this 5 | ]# and this 6 | # here too 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-more-mixed-types.toml: -------------------------------------------------------------------------------- 1 | contributors = [ 2 | "Foo Bar ", 3 | { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } 4 | ] -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-more-mixed-types.yaml: -------------------------------------------------------------------------------- 1 | contributors: [ 2 | "Foo Bar ", 3 | { name: "Baz Qux", email: "bazqux@example.com", url: "https://example.com/bazqux" } 4 | ] 5 | -------------------------------------------------------------------------------- /tests/examples/invalid/array_with_invalid_chars.toml: -------------------------------------------------------------------------------- 1 | array = [ 2 | "This might most likely happen in multiline arrays", 3 | Like here, 4 | "or here, 5 | and here" 6 | ] 7 | -------------------------------------------------------------------------------- /tests/examples/invalid/key_value_with_trailing_chars.toml: -------------------------------------------------------------------------------- 1 | string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this 2 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-extend-dotted-object-1.toml: -------------------------------------------------------------------------------- 1 | # This makes the key "fruit" into a table. 2 | fruit.apple.smooth = true 3 | 4 | # So then you can add to the table "fruit" like so: 5 | fruit.orange = 2 6 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-extend-dotted-object-2.yaml: -------------------------------------------------------------------------------- 1 | apple: 2 | type: "fruit" 3 | skin: "thin" 4 | color: "red" 5 | 6 | orange: 7 | type: "fruit" 8 | skin: "thick" 9 | color: "orange" 10 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-extend-dotted-object-3.yaml: -------------------------------------------------------------------------------- 1 | apple: 2 | type: "fruit" 3 | skin: "thin" 4 | color: "red" 5 | 6 | orange: 7 | type: "fruit" 8 | skin: "thick" 9 | color: "orange" 10 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-of-tables-1.toml: -------------------------------------------------------------------------------- 1 | [[products]] 2 | name = "Hammer" 3 | sku = 738594937 4 | 5 | [[products]] 6 | 7 | [[products]] 8 | name = "Nail" 9 | sku = 284758393 10 | color = "gray" 11 | -------------------------------------------------------------------------------- /tests/examples/table_names.toml: -------------------------------------------------------------------------------- 1 | ['Special "table"'] 2 | foo = "bar" 3 | 4 | ["BJ's Restaurant"] 5 | account = "dining" 6 | 7 | ["]"] 8 | foo = 1 9 | 10 | [ "[bracket]" ] 11 | bar = 2 12 | 13 | [ a . "b.c" . d ] 14 | baz = 3 15 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-extend-dotted-object-3.toml: -------------------------------------------------------------------------------- 1 | # RECOMMENDED 2 | 3 | apple.type = "fruit" 4 | apple.skin = "thin" 5 | apple.color = "red" 6 | 7 | orange.type = "fruit" 8 | orange.skin = "thick" 9 | orange.color = "orange" 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = True 3 | envlist = py38, py39, py310, py311, py312, pypy3 4 | 5 | [testenv] 6 | whitelist_externals = poetry 7 | skip_install = true 8 | commands = 9 | poetry install -v 10 | poetry run pytest tests/ 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/toml-test"] 2 | path = tests/toml-test 3 | url = https://github.com/BurntSushi/toml-test.git 4 | [submodule "tests/toml-spec-tests"] 5 | path = tests/toml-spec-tests 6 | url = https://github.com/iarna/toml-spec-tests.git 7 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-extend-dotted-object-2.toml: -------------------------------------------------------------------------------- 1 | # VALID BUT DISCOURAGED 2 | 3 | apple.type = "fruit" 4 | orange.type = "fruit" 5 | 6 | apple.skin = "thin" 7 | orange.skin = "thick" 8 | 9 | apple.color = "red" 10 | orange.color = "orange" 11 | -------------------------------------------------------------------------------- /tests/examples/fruit.toml: -------------------------------------------------------------------------------- 1 | [[fruit.blah]] 2 | name = "apple" 3 | 4 | [fruit.blah.physical] 5 | color = "red" 6 | shape = "round" 7 | 8 | [[fruit.blah]] 9 | name = "banana" 10 | 11 | [fruit.blah.physical] 12 | color = "yellow" 13 | shape = "bent" 14 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/array-of-tables-2.toml: -------------------------------------------------------------------------------- 1 | # INVALID TOML DOC 2 | [[fruit]] 3 | name = "apple" 4 | 5 | [[fruit.variety]] 6 | name = "red delicious" 7 | 8 | # This table conflicts with the previous table 9 | [fruit.variety] 10 | name = "granny smith" 11 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/multiple-dot-key.toml: -------------------------------------------------------------------------------- 1 | # THE FOLLOWING IS INVALID 2 | 3 | # This defines the value of fruit.apple to be an integer. 4 | fruit.apple = 1 5 | 6 | # But then this treats fruit.apple like it's a table. 7 | # You can't turn an integer into a table. 8 | fruit.apple.smooth = true 9 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/table-invalid-1.toml: -------------------------------------------------------------------------------- 1 | [fruit.physical] # subtable, but to which parent element should it belong? 2 | color = "red" 3 | shape = "round" 4 | 5 | [[fruit]] # parser must throw an error upon discovering that "fruit" is 6 | # an array rather than a table 7 | name = "apple" -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/table-invalid-4.toml: -------------------------------------------------------------------------------- 1 | # INVALID TOML DOC 2 | [[fruit]] 3 | name = "apple" 4 | 5 | [[fruit.variety]] 6 | name = "red delicious" 7 | 8 | [fruit.physical] 9 | color = "red" 10 | shape = "round" 11 | 12 | # INVALID: This array of tables conflicts with the previous table 13 | [[fruit.physical]] 14 | color = "green" 15 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/errors/table-invalid-3.toml: -------------------------------------------------------------------------------- 1 | # INVALID TOML DOC 2 | [[fruit]] 3 | name = "apple" 4 | 5 | [[fruit.variety]] 6 | name = "red delicious" 7 | 8 | # INVALID: This table conflicts with the previous array of tables 9 | [fruit.variety] 10 | name = "granny smith" 11 | 12 | [fruit.physical] 13 | color = "red" 14 | shape = "round" 15 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-of-tables-2.toml: -------------------------------------------------------------------------------- 1 | [[fruit]] 2 | name = "apple" 3 | 4 | [fruit.physical] 5 | color = "red" 6 | shape = "round" 7 | 8 | [[fruit.variety]] 9 | name = "red delicious" 10 | 11 | [[fruit.variety]] 12 | name = "granny smith" 13 | 14 | [[fruit]] 15 | name = "banana" 16 | 17 | [[fruit.variety]] 18 | name = "plantain" 19 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-array-of-tables-2.yaml: -------------------------------------------------------------------------------- 1 | fruit: [ 2 | { 3 | name: "apple", 4 | physical: { 5 | color: "red", 6 | shape: "round" 7 | }, 8 | variety: [ 9 | { name: "red delicious" }, 10 | { name: "granny smith" } 11 | ] 12 | }, 13 | { 14 | name: "banana", 15 | variety: [ 16 | { name: "plantain" } 17 | ] 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /tests/examples/json/table_names.json: -------------------------------------------------------------------------------- 1 | { 2 | "Special \"table\"": { 3 | "foo": "bar" 4 | }, 5 | "BJ's Restaurant": { 6 | "account": "dining" 7 | }, 8 | "]": { 9 | "foo": 1 10 | }, 11 | "[bracket]": { 12 | "bar": 2 13 | }, 14 | "a": { 15 | "b.c": { 16 | "d": { 17 | "baz": 3 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | _build 9 | .cache 10 | *.so 11 | 12 | # Installer logs 13 | pip-log.txt 14 | 15 | # Unit test / coverage reports 16 | .coverage 17 | .tox 18 | .pytest_cache 19 | 20 | .DS_Store 21 | .idea/* 22 | .python-version 23 | .vscode/* 24 | 25 | /test.py 26 | /test_*.* 27 | 28 | setup.cfg 29 | MANIFEST.in 30 | /setup.py 31 | /docs/site/* 32 | /tests/fixtures/simple_project/setup.py 33 | .mypy_cache 34 | benchmark.py 35 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # .readthedocs.yaml 4 | # Read the Docs configuration file 5 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html 6 | # for details 7 | 8 | version: 2 9 | 10 | sphinx: 11 | builder: dirhtml 12 | configuration: docs/conf.py 13 | fail_on_warning: true 14 | 15 | build: 16 | os: ubuntu-22.04 17 | tools: 18 | python: >- 19 | 3.11 20 | 21 | python: 22 | install: 23 | - method: pip 24 | path: . 25 | - requirements: docs/requirements.txt 26 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-readme-example.yaml: -------------------------------------------------------------------------------- 1 | # This is a TOML document. 2 | 3 | title: "TOML Example" 4 | 5 | owner: 6 | name: "Tom Preston-Werner" 7 | dob: 1979-05-27T07:32:00-08:00 # First class dates 8 | 9 | database: 10 | server: "192.168.1.1" 11 | ports: [ 8001, 8001, 8002 ] 12 | connection_max: 5000 13 | enabled: true 14 | 15 | servers: 16 | alpha: 17 | ip: "10.0.0.1" 18 | dc: "eqdc10" 19 | beta: 20 | ip: "10.0.0.2" 21 | dc: "eqdc10" 22 | 23 | clients: 24 | data: [ ["gamma", "delta"], [1, 2] ] 25 | hosts: [ 26 | "alpha", 27 | "omega" 28 | ] 29 | -------------------------------------------------------------------------------- /tomlkit/_compat.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import contextlib 4 | import sys 5 | 6 | from typing import Any 7 | 8 | 9 | PY38 = sys.version_info >= (3, 8) 10 | 11 | 12 | def decode(string: Any, encodings: list[str] | None = None): 13 | if not isinstance(string, bytes): 14 | return string 15 | 16 | encodings = encodings or ["utf-8", "latin1", "ascii"] 17 | 18 | for encoding in encodings: 19 | with contextlib.suppress(UnicodeEncodeError, UnicodeDecodeError): 20 | return string.decode(encoding) 21 | 22 | return string.decode(encodings[0], errors="ignore") 23 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v6.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | exclude: ^tests/(toml-test|toml-spec-tests)/.* 7 | - id: end-of-file-fixer 8 | exclude: ^tests/(toml-test|toml-spec-tests)/.* 9 | - id: debug-statements 10 | 11 | - repo: https://github.com/asottile/pyupgrade 12 | rev: v3.21.0 13 | hooks: 14 | - id: pyupgrade 15 | args: [--py37-plus] 16 | 17 | - repo: https://github.com/astral-sh/ruff-pre-commit 18 | rev: 'v0.14.3' 19 | hooks: 20 | - id: ruff 21 | args: [--fix, --exit-non-zero-on-fix, --show-fixes] 22 | - id: ruff-format 23 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/README.md: -------------------------------------------------------------------------------- 1 | These are the spec tests for TOML used by @iarna/toml. 2 | 3 | The errors folder contains TOML files that should cause a parser to report an error. 4 | 5 | The values folder contains TOML files and paired YAML or JSON files. The 6 | YAML files should parse to a structure that's deeply equal to the TOML 7 | structure. The JSON files match the patterns found in [BurntSushi 0.4 TOML 8 | tests](https://github.com/BurntSushi/toml-test#json-encoding). 9 | 10 | We introduce the following new types to match TOML 0.5.0: 11 | 12 | * _datetime-local_ - A datetime without a timezone. Floating. 13 | * _date_ - A date without any time component 14 | * _time_ - A time without any date component 15 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/spec-readme-example.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML document. 2 | 3 | title = "TOML Example" 4 | 5 | [owner] 6 | name = "Tom Preston-Werner" 7 | dob = 1979-05-27T07:32:00-08:00 # First class dates 8 | 9 | [database] 10 | server = "192.168.1.1" 11 | ports = [ 8001, 8001, 8002 ] 12 | connection_max = 5000 13 | enabled = true 14 | 15 | [servers] 16 | 17 | # Indentation (tabs and/or spaces) is allowed but not required 18 | [servers.alpha] 19 | ip = "10.0.0.1" 20 | dc = "eqdc10" 21 | 22 | [servers.beta] 23 | ip = "10.0.0.2" 24 | dc = "eqdc10" 25 | 26 | [clients] 27 | data = [ ["gamma", "delta"], [1, 2] ] 28 | 29 | # Line breaks are OK when inside arrays 30 | hosts = [ 31 | "alpha", 32 | "omega" 33 | ] 34 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Rebecca Turner 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /tests/examples/json/0.5.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "1979-05-27", 3 | "time": "07:32:00", 4 | "plus-infinity": Infinity, 5 | "sf1": Infinity, 6 | "sf2": Infinity, 7 | "sf3": -Infinity, 8 | "sf4": NaN, 9 | "sf5": NaN, 10 | "sf6": NaN, 11 | "hex1": 3735928559, 12 | "hex2": 3735928559, 13 | "hex3": 3735928559, 14 | "oct1": 342391, 15 | "oct2": 493, 16 | "bin1": 214, 17 | "physical": { 18 | "color": "orange", 19 | "shape": "round" 20 | }, 21 | "site": { 22 | "google.com": true 23 | }, 24 | "a": { 25 | "b": { 26 | "c": 1, 27 | "d": 2 28 | } 29 | }, 30 | "table": { 31 | "a": { 32 | "b": { 33 | "c": 1, 34 | "d": 2 35 | }, 36 | "c": 3 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/examples/0.5.0.toml: -------------------------------------------------------------------------------- 1 | date = 1979-05-27 2 | time = 07:32:00 3 | 4 | plus-infinity = +inf 5 | # infinity 6 | sf1 = inf # positive infinity 7 | sf2 = +inf # positive infinity 8 | sf3 = -inf # negative infinity 9 | 10 | # not a number 11 | sf4 = nan # actual sNaN/qNaN encoding is implementation specific 12 | sf5 = +nan # same as `nan` 13 | sf6 = -nan # valid, actual encoding is implementation specific 14 | 15 | # hexadecimal with prefix `0x` 16 | hex1 = 0xDEADBEEF 17 | hex2 = 0xdeadbeef 18 | hex3 = 0xdead_beef 19 | 20 | # octal with prefix `0o` 21 | oct1 = 0o01234567 22 | oct2 = 0o755 # useful for Unix file permissions 23 | 24 | # binary with prefix `0b` 25 | bin1 = 0b11010110 26 | 27 | # Dotted keys 28 | physical.color = "orange" 29 | physical.shape = "round" 30 | site."google.com" = true 31 | 32 | a.b.c = 1 33 | a.b.d = 2 34 | 35 | 36 | [table] 37 | a.b.c = 1 38 | a.b.d = 2 39 | a.c = 3 40 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | Public API 5 | ---------- 6 | 7 | Functions you need to create TOML elements to construct 8 | a TOML document. 9 | 10 | .. automodule:: tomlkit 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | TOML Document 16 | ------------- 17 | 18 | .. module:: tomlkit.toml_document 19 | 20 | .. autoclass:: TOMLDocument 21 | :show-inheritance: 22 | :inherited-members: 23 | 24 | 25 | TOML File 26 | --------- 27 | 28 | .. module:: tomlkit.toml_file 29 | 30 | .. autoclass:: TOMLFile 31 | :show-inheritance: 32 | :members: 33 | 34 | 35 | TOML Items 36 | ---------- 37 | 38 | .. automodule:: tomlkit.items 39 | :show-inheritance: 40 | :members: 41 | :exclude-members: item, AbstractTable 42 | 43 | TOML Exceptions 44 | --------------- 45 | 46 | .. automodule:: tomlkit.exceptions 47 | :members: 48 | :show-inheritance: 49 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /tests/test_write.py: -------------------------------------------------------------------------------- 1 | from tomlkit import dumps 2 | from tomlkit import loads 3 | 4 | 5 | def test_write_backslash(): 6 | d = {"foo": "\\e\u25e6\r"} 7 | 8 | expected = """foo = "\\\\e\u25e6\\r" 9 | """ 10 | 11 | assert expected == dumps(d) 12 | assert loads(dumps(d))["foo"] == "\\e\u25e6\r" 13 | 14 | 15 | def test_escape_special_characters_in_key(): 16 | d = {"foo\nbar": "baz"} 17 | expected = '"foo\\nbar" = "baz"\n' 18 | assert expected == dumps(d) 19 | assert loads(dumps(d))["foo\nbar"] == "baz" 20 | 21 | 22 | def test_write_inline_table_in_nested_arrays(): 23 | d = {"foo": [[{"a": 1}]]} 24 | expected = "foo = [[{a = 1}]]\n" 25 | assert expected == dumps(d) 26 | assert loads(dumps(d))["foo"] == [[{"a": 1}]] 27 | 28 | 29 | def test_serialize_aot_with_nested_tables(): 30 | doc = {"a": [{"b": {"c": 1}}]} 31 | expected = """\ 32 | [[a]] 33 | [a.b] 34 | c = 1 35 | """ 36 | assert dumps(doc) == expected 37 | assert loads(expected) == doc 38 | -------------------------------------------------------------------------------- /tests/examples/example.toml: -------------------------------------------------------------------------------- 1 | # This is a TOML document. Boom. 2 | 3 | title = "TOML Example" 4 | 5 | [owner] 6 | name = "Tom Preston-Werner" 7 | organization = "GitHub" 8 | bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." 9 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 10 | 11 | [database] 12 | server = "192.168.1.1" 13 | ports = [8001, 8001, 8002] 14 | connection_max = 5000 15 | enabled = true 16 | 17 | [servers] 18 | 19 | # You can indent as you please. Tabs or spaces. TOML don't care. 20 | [servers.alpha] 21 | ip = "10.0.0.1" 22 | dc = "eqdc10" 23 | 24 | [servers.beta] 25 | ip = "10.0.0.2" 26 | dc = "eqdc10" 27 | country = "中国" # This should be parsed as UTF-8 28 | 29 | [clients] 30 | data = [["gamma", "delta"], [1, 2]] # just an update to make sure parsers support it 31 | 32 | # Line breaks are OK when inside arrays 33 | hosts = [ 34 | "alpha", 35 | "omega" 36 | ] 37 | 38 | # Products 39 | [[products]] 40 | name = "Hammer" 41 | sku = 738594937 42 | 43 | [[products]] 44 | name = "Nail" 45 | sku = 284758393 46 | color = "gray" 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Sébastien Eustace 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*.*.*" 7 | 8 | jobs: 9 | Release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | with: 16 | submodules: "recursive" 17 | 18 | - name: Get tag 19 | id: tag 20 | run: echo ::set-output name=tag::${GITHUB_REF#refs/tags/} 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v4 24 | with: 25 | python-version: 3.x 26 | 27 | - name: Install and set up Poetry 28 | run: pipx install poetry 29 | 30 | - name: Build project for distribution 31 | run: poetry build 32 | 33 | - name: Check Version 34 | id: check-version 35 | run: | 36 | [[ "$(poetry version --short)" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \ 37 | || echo ::set-output name=prerelease::true 38 | - name: Create Release 39 | uses: ncipollo/release-action@v1 40 | with: 41 | artifacts: "dist/*" 42 | draft: false 43 | allowUpdates: true 44 | generateReleaseNotes: true 45 | prerelease: steps.check-version.outputs.prerelease == 'true' 46 | 47 | - name: Publish to PyPI 48 | env: 49 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} 50 | run: poetry publish 51 | -------------------------------------------------------------------------------- /tests/test_parser.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from tomlkit.exceptions import EmptyTableNameError 4 | from tomlkit.exceptions import InternalParserError 5 | from tomlkit.exceptions import UnexpectedCharError 6 | from tomlkit.items import StringType 7 | from tomlkit.parser import Parser 8 | 9 | 10 | def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string(): 11 | parser = Parser('"foo"') 12 | 13 | with pytest.raises(InternalParserError) as e: 14 | parser._parse_string(StringType.SLL) 15 | 16 | assert e.value.line == 1 17 | assert e.value.col == 0 18 | 19 | 20 | def test_parser_should_raise_an_error_for_empty_tables(): 21 | content = """ 22 | [one] 23 | [] 24 | """ 25 | 26 | parser = Parser(content) 27 | 28 | with pytest.raises(EmptyTableNameError) as e: 29 | parser.parse() 30 | 31 | assert e.value.line == 3 32 | assert e.value.col == 1 33 | 34 | 35 | def test_parser_should_raise_an_error_if_equal_not_found(): 36 | content = """[foo] 37 | a {c = 1, d = 2} 38 | """ 39 | parser = Parser(content) 40 | with pytest.raises(UnexpectedCharError): 41 | parser.parse() 42 | 43 | 44 | def test_parse_multiline_string_ignore_the_first_newline(): 45 | content = 'a = """\nfoo\n"""' 46 | parser = Parser(content) 47 | assert parser.parse() == {"a": "foo\n"} 48 | 49 | content = 'a = """\r\nfoo\n"""' 50 | parser = Parser(content) 51 | assert parser.parse() == {"a": "foo\n"} 52 | -------------------------------------------------------------------------------- /tomlkit/toml_char.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | 4 | class TOMLChar(str): 5 | def __init__(self, c): 6 | super().__init__() 7 | 8 | if len(self) > 1: 9 | raise ValueError("A TOML character must be of length 1") 10 | 11 | BARE = string.ascii_letters + string.digits + "-_" 12 | KV = "= \t" 13 | NUMBER = string.digits + "+-_.e" 14 | SPACES = " \t" 15 | NL = "\n\r" 16 | WS = SPACES + NL 17 | 18 | def is_bare_key_char(self) -> bool: 19 | """ 20 | Whether the character is a valid bare key name or not. 21 | """ 22 | return self in self.BARE 23 | 24 | def is_kv_sep(self) -> bool: 25 | """ 26 | Whether the character is a valid key/value separator or not. 27 | """ 28 | return self in self.KV 29 | 30 | def is_int_float_char(self) -> bool: 31 | """ 32 | Whether the character if a valid integer or float value character or not. 33 | """ 34 | return self in self.NUMBER 35 | 36 | def is_ws(self) -> bool: 37 | """ 38 | Whether the character is a whitespace character or not. 39 | """ 40 | return self in self.WS 41 | 42 | def is_nl(self) -> bool: 43 | """ 44 | Whether the character is a new line character or not. 45 | """ 46 | return self in self.NL 47 | 48 | def is_spaces(self) -> bool: 49 | """ 50 | Whether the character is a space or not 51 | """ 52 | return self in self.SPACES 53 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "tomlkit" 3 | version = "0.13.3" 4 | description = "Style preserving TOML library" 5 | authors = [ 6 | "Sébastien Eustace ", 7 | "Frost Ming " 8 | ] 9 | license = "MIT" 10 | 11 | readme = "README.md" 12 | 13 | homepage = "https://github.com/sdispater/tomlkit" 14 | repository = "https://github.com/sdispater/tomlkit" 15 | 16 | include = [ 17 | { path = "tomlkit/py.typed" }, 18 | { path = "tests", format = "sdist" }, 19 | { path = "docs", format = "sdist" }, 20 | { path = "CHANGELOG.md", format = "sdist" }, 21 | ] 22 | 23 | [tool.poetry.dependencies] 24 | python = ">=3.8" 25 | 26 | [tool.poetry.group.dev.dependencies] 27 | pytest = "^7.2.0" 28 | pytest-cov = "^4.0.0" 29 | PyYAML = "^6.0" 30 | pre-commit = "^2.20.0" 31 | mypy = "^0.990" 32 | Sphinx = "^4.3.2" 33 | furo = "^2022.9.29" 34 | 35 | [tool.ruff.lint] 36 | extend-select = [ 37 | "I", # isort 38 | "B", # flake8-bugbear 39 | "C4", # flake8-comprehensions 40 | "PGH", # pygrep-hooks 41 | "RUF", # ruff 42 | "W", # pycodestyle 43 | "YTT", # flake8-2020 44 | ] 45 | extend-ignore = ["B018", "B019", "RUF018"] 46 | 47 | [tool.ruff.lint.mccabe] 48 | max-complexity = 10 49 | 50 | [tool.ruff.lint.isort] 51 | known-first-party = ["tomlkit"] 52 | known-third-party = ["pytest"] 53 | force-single-line = true 54 | lines-after-imports = 2 55 | lines-between-types = 1 56 | 57 | [build-system] 58 | requires = ["poetry-core>=1.0.0a9"] 59 | build-backend = "poetry.core.masonry.api" 60 | -------------------------------------------------------------------------------- /tests/util.py: -------------------------------------------------------------------------------- 1 | from tomlkit.items import AoT 2 | from tomlkit.items import Array 3 | from tomlkit.items import Bool 4 | from tomlkit.items import Comment 5 | from tomlkit.items import Date 6 | from tomlkit.items import DateTime 7 | from tomlkit.items import Float 8 | from tomlkit.items import InlineTable 9 | from tomlkit.items import Integer 10 | from tomlkit.items import Item 11 | from tomlkit.items import KeyType 12 | from tomlkit.items import Null 13 | from tomlkit.items import SingleKey as Key 14 | from tomlkit.items import String 15 | from tomlkit.items import StringType 16 | from tomlkit.items import Table 17 | from tomlkit.items import Time 18 | from tomlkit.items import Trivia 19 | from tomlkit.toml_document import TOMLDocument 20 | 21 | 22 | TOMLKIT_TYPES = [ 23 | Bool, 24 | Comment, 25 | InlineTable, 26 | Integer, 27 | Float, 28 | DateTime, 29 | Date, 30 | Time, 31 | Array, 32 | KeyType, 33 | Key, 34 | String, 35 | StringType, 36 | Table, 37 | Trivia, 38 | Item, 39 | AoT, 40 | Null, 41 | TOMLDocument, 42 | ] 43 | 44 | 45 | def assert_not_tomlkit_type(v): 46 | for _, tomlkit_type in enumerate(TOMLKIT_TYPES): 47 | assert not isinstance(v, tomlkit_type) 48 | 49 | 50 | def assert_is_ppo(v_unwrapped, unwrapped_type): 51 | assert_not_tomlkit_type(v_unwrapped) 52 | assert isinstance(v_unwrapped, unwrapped_type) 53 | 54 | 55 | def elementary_test(v, unwrapped_type): 56 | v_unwrapped = v.unwrap() 57 | assert_is_ppo(v_unwrapped, unwrapped_type) 58 | -------------------------------------------------------------------------------- /tests/examples/hard.toml: -------------------------------------------------------------------------------- 1 | # Test file for TOML 2 | # Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate 3 | # This part you'll really hate 4 | 5 | [the] 6 | test_string = "You'll hate me after this - #" # " Annoying, isn't it? 7 | 8 | [the.hard] 9 | test_array = [ "] ", " # "] # ] There you go, parse this! 10 | test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] 11 | # You didn't think it'd as easy as chucking out the last #, did you? 12 | another_test_string = " Same thing, but with a string #" 13 | harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" 14 | # Things will get harder 15 | 16 | [the.hard."bit#"] 17 | "what?" = "You don't think some user won't do that?" 18 | multi_line_array = [ 19 | "]", 20 | # ] Oh yes I did 21 | ] 22 | 23 | # Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test 24 | 25 | #[error] if you didn't catch this, your parser is broken 26 | #string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this 27 | #array = [ 28 | # "This might most likely happen in multiline arrays", 29 | # Like here, 30 | # "or here, 31 | # and here" 32 | # ] End of array comment, forgot the # 33 | #number = 3.14 pi <--again forgot the # 34 | -------------------------------------------------------------------------------- /tomlkit/__init__.py: -------------------------------------------------------------------------------- 1 | from tomlkit.api import TOMLDocument 2 | from tomlkit.api import aot 3 | from tomlkit.api import array 4 | from tomlkit.api import boolean 5 | from tomlkit.api import comment 6 | from tomlkit.api import date 7 | from tomlkit.api import datetime 8 | from tomlkit.api import document 9 | from tomlkit.api import dump 10 | from tomlkit.api import dumps 11 | from tomlkit.api import float_ 12 | from tomlkit.api import inline_table 13 | from tomlkit.api import integer 14 | from tomlkit.api import item 15 | from tomlkit.api import key 16 | from tomlkit.api import key_value 17 | from tomlkit.api import load 18 | from tomlkit.api import loads 19 | from tomlkit.api import nl 20 | from tomlkit.api import parse 21 | from tomlkit.api import register_encoder 22 | from tomlkit.api import string 23 | from tomlkit.api import table 24 | from tomlkit.api import time 25 | from tomlkit.api import unregister_encoder 26 | from tomlkit.api import value 27 | from tomlkit.api import ws 28 | 29 | 30 | __version__ = "0.13.3" 31 | __all__ = [ 32 | "TOMLDocument", 33 | "aot", 34 | "array", 35 | "boolean", 36 | "comment", 37 | "date", 38 | "datetime", 39 | "document", 40 | "dump", 41 | "dumps", 42 | "float_", 43 | "inline_table", 44 | "integer", 45 | "item", 46 | "key", 47 | "key_value", 48 | "load", 49 | "loads", 50 | "nl", 51 | "parse", 52 | "register_encoder", 53 | "string", 54 | "table", 55 | "time", 56 | "unregister_encoder", 57 | "value", 58 | "ws", 59 | ] 60 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile docs/requirements.in -o docs/requirements.txt 3 | alabaster==0.7.16 4 | # via sphinx 5 | babel==2.15.0 6 | # via sphinx 7 | beautifulsoup4==4.12.3 8 | # via furo 9 | certifi==2024.7.4 10 | # via requests 11 | charset-normalizer==3.3.2 12 | # via requests 13 | docutils==0.21.2 14 | # via sphinx 15 | furo==2024.7.18 16 | # via -r requirements.in 17 | idna==3.7 18 | # via requests 19 | imagesize==1.4.1 20 | # via sphinx 21 | importlib-metadata==8.7.0 22 | # via sphinx 23 | jinja2==3.1.6 24 | # via sphinx 25 | markupsafe==2.1.5 26 | # via jinja2 27 | packaging==24.1 28 | # via sphinx 29 | pygments==2.18.0 30 | # via 31 | # furo 32 | # sphinx 33 | requests==2.32.4 34 | # via sphinx 35 | snowballstemmer==2.2.0 36 | # via sphinx 37 | soupsieve==2.5 38 | # via beautifulsoup4 39 | sphinx==7.4.7 40 | # via 41 | # -r requirements.in 42 | # furo 43 | # sphinx-basic-ng 44 | sphinx-basic-ng==1.0.0b2 45 | # via furo 46 | sphinxcontrib-applehelp==1.0.8 47 | # via sphinx 48 | sphinxcontrib-devhelp==1.0.6 49 | # via sphinx 50 | sphinxcontrib-htmlhelp==2.0.6 51 | # via sphinx 52 | sphinxcontrib-jsmath==1.0.1 53 | # via sphinx 54 | sphinxcontrib-qthelp==1.0.8 55 | # via sphinx 56 | sphinxcontrib-serializinghtml==1.1.10 57 | # via sphinx 58 | tomli==2.2.1 59 | # via sphinx 60 | urllib3==2.5.0 61 | # via requests 62 | zipp==3.23.0 63 | # via importlib-metadata 64 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from datetime import datetime as dt 3 | from datetime import time 4 | from datetime import timedelta as td 5 | from datetime import timezone as tz 6 | 7 | import pytest 8 | 9 | from tomlkit._utils import _utc 10 | from tomlkit._utils import parse_rfc3339 11 | 12 | 13 | @pytest.mark.parametrize( 14 | "string, expected", 15 | [ 16 | ("1979-05-27T07:32:00", dt(1979, 5, 27, 7, 32, 0)), 17 | ("1979-05-27T07:32:00Z", dt(1979, 5, 27, 7, 32, 0, tzinfo=_utc)), 18 | ( 19 | "1979-05-27T07:32:00-07:00", 20 | dt(1979, 5, 27, 7, 32, 0, tzinfo=tz(td(seconds=-7 * 3600), "-07:00")), 21 | ), 22 | ( 23 | "1979-05-27T00:32:00.999999-07:00", 24 | dt( 25 | 1979, 26 | 5, 27 | 27, 28 | 0, 29 | 32, 30 | 0, 31 | 999999, 32 | tzinfo=tz(td(seconds=-7 * 3600), "-07:00"), 33 | ), 34 | ), 35 | ], 36 | ) 37 | def test_parse_rfc3339_datetime(string, expected): 38 | assert parse_rfc3339(string) == expected 39 | 40 | 41 | @pytest.mark.parametrize("string, expected", [("1979-05-27", date(1979, 5, 27))]) 42 | def test_parse_rfc3339_date(string, expected): 43 | assert parse_rfc3339(string) == expected 44 | 45 | 46 | @pytest.mark.parametrize( 47 | "string, expected", 48 | [("12:34:56", time(12, 34, 56)), ("12:34:56.123456", time(12, 34, 56, 123456))], 49 | ) 50 | def test_parse_rfc3339_time(string, expected): 51 | assert parse_rfc3339(string) == expected 52 | -------------------------------------------------------------------------------- /tests/test_toml_tests.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | 5 | from tomlkit import load 6 | from tomlkit import parse 7 | from tomlkit._compat import decode 8 | from tomlkit._utils import parse_rfc3339 9 | from tomlkit.exceptions import TOMLKitError 10 | 11 | 12 | def to_bool(s): 13 | assert s in ["true", "false"] 14 | 15 | return s == "true" 16 | 17 | 18 | stypes = { 19 | "string": str, 20 | "bool": to_bool, 21 | "integer": int, 22 | "float": float, 23 | "datetime": parse_rfc3339, 24 | "datetime-local": parse_rfc3339, 25 | "date-local": parse_rfc3339, 26 | "time-local": parse_rfc3339, 27 | } 28 | 29 | 30 | def untag(value): 31 | if isinstance(value, list): 32 | return [untag(i) for i in value] 33 | elif "type" in value and "value" in value and len(value) == 2: 34 | if value["type"] in stypes: 35 | val = decode(value["value"]) 36 | 37 | return stypes[value["type"]](val) 38 | elif value["type"] == "array": 39 | return [untag(i) for i in value["value"]] 40 | else: 41 | raise Exception(f"Unsupported type {value['type']}") 42 | else: 43 | return {k: untag(v) for k, v in value.items()} 44 | 45 | 46 | def test_valid_decode(valid_case): 47 | json_val = untag(json.loads(valid_case["json"])) 48 | toml_val = parse(valid_case["toml"]) 49 | 50 | assert toml_val == json_val 51 | assert toml_val.as_string() == valid_case["toml"] 52 | 53 | 54 | def test_invalid_decode(invalid_decode_case): 55 | with pytest.raises(TOMLKitError): 56 | parse(invalid_decode_case["toml"]) 57 | 58 | 59 | def test_invalid_encode(invalid_encode_case): 60 | with open(invalid_encode_case, encoding="utf-8") as f: 61 | with pytest.raises((TOMLKitError, UnicodeDecodeError)): 62 | load(f) 63 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. TOMLKit documentation master file, created by 2 | sphinx-quickstart on Fri Dec 24 09:31:46 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | TOML Kit 7 | ======== 8 | 9 | **Style-preserving TOML library for Python** 10 | 11 | .. image:: https://img.shields.io/pypi/v/tomlkit.svg?logo=python&logoColor=white 12 | :target: https://pypi.org/project/tomlkit/ 13 | .. image:: https://img.shields.io/pypi/pyversions/tomlkit.svg?logo=python&logoColor=white 14 | :target: https://pypi.org/project/tomlkit/ 15 | .. image:: https://img.shields.io/github/license/sdispater/tomlkit.svg?logo=github&logoColor=white 16 | :target: https://github.com/sdispater/tomlkit/blob/master/LICENSE 17 | .. image:: https://img.shields.io/badge/TOML-1.0.0-9c4221 18 | :target: https://toml.io/en/v1.0.0 19 | 20 | TOML Kit is a **1.0.0-compliant** `TOML `_ library. 21 | 22 | It includes a parser that preserves all comments, indentations, whitespace and internal element ordering, 23 | and makes them accessible and editable via an intuitive API. 24 | 25 | You can also create new TOML documents from scratch using the provided helpers. 26 | 27 | Part of the implementation as been adapted, improved and fixed from `Molten `_. 28 | 29 | Installation 30 | ------------ 31 | 32 | If you are using `Poetry `_, 33 | add ``tomlkit`` to your ``pyproject.toml`` file by using:: 34 | 35 | poetry add tomlkit 36 | 37 | If not, you can use ``pip``:: 38 | 39 | pip install tomlkit 40 | 41 | 42 | Contents 43 | -------- 44 | 45 | .. toctree:: 46 | :maxdepth: 2 47 | 48 | quickstart 49 | api 50 | 51 | 52 | Indices and tables 53 | ================== 54 | 55 | * :ref:`genindex` 56 | * :ref:`modindex` 57 | * :ref:`search` 58 | -------------------------------------------------------------------------------- /tomlkit/toml_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from typing import TYPE_CHECKING 5 | 6 | from tomlkit.api import loads 7 | from tomlkit.toml_document import TOMLDocument 8 | 9 | 10 | if TYPE_CHECKING: 11 | from _typeshed import StrPath as _StrPath 12 | else: 13 | from typing import Union 14 | 15 | _StrPath = Union[str, os.PathLike] 16 | 17 | 18 | class TOMLFile: 19 | """ 20 | Represents a TOML file. 21 | 22 | :param path: path to the TOML file 23 | """ 24 | 25 | def __init__(self, path: _StrPath) -> None: 26 | self._path = path 27 | self._linesep = os.linesep 28 | 29 | def read(self) -> TOMLDocument: 30 | """Read the file content as a :class:`tomlkit.toml_document.TOMLDocument`.""" 31 | with open(self._path, encoding="utf-8", newline="") as f: 32 | content = f.read() 33 | 34 | # check if consistent line endings 35 | num_newline = content.count("\n") 36 | if num_newline > 0: 37 | num_win_eol = content.count("\r\n") 38 | if num_win_eol == num_newline: 39 | self._linesep = "\r\n" 40 | content = content.replace("\r\n", "\n") 41 | elif num_win_eol == 0: 42 | self._linesep = "\n" 43 | else: 44 | self._linesep = "mixed" 45 | 46 | return loads(content) 47 | 48 | def write(self, data: TOMLDocument) -> None: 49 | """Write the TOMLDocument to the file.""" 50 | content = data.as_string() 51 | 52 | # apply linesep 53 | if self._linesep == "\n": 54 | content = content.replace("\r\n", "\n") 55 | elif self._linesep == "\r\n": 56 | content = re.sub(r"(?' 59 | 60 | regex2 = '''I [dw]on't need \d{2} apples''' 61 | lines = ''' 62 | The first newline is 63 | trimmed in raw strings. 64 | All other whitespace 65 | is preserved. 66 | ''' 67 | 68 | 69 | [[fruit]] 70 | name = "apple" 71 | 72 | [fruit.physical] 73 | color = "red" 74 | shape = "round" 75 | 76 | [[fruit.variety]] 77 | name = "red delicious" 78 | 79 | [[fruit.variety]] 80 | name = "granny smith" 81 | 82 | [[fruit]] 83 | name = "banana" 84 | 85 | [[fruit.variety]] 86 | name = "plantain" 87 | 88 | 89 | points = [ { x = 1, y = 2, z = 3 }, # This value is so special to me 90 | { x = 7, y = 8, z = 9 }, 91 | { x = 2, y = 4, z = 8 } ] 92 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/qa-array-inline-nested-1000.toml: -------------------------------------------------------------------------------- 1 | key = [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] ] 2 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | pull_request: 9 | branches: 10 | - "**" 11 | 12 | jobs: 13 | Tests: 14 | name: ${{ matrix.os }} / ${{ matrix.python-version }} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ubuntu-latest, macos-13, windows-latest] 20 | python-version: [3.8, 3.9, "3.10", 3.11, 3.12, 3.13] 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | submodules: "recursive" 26 | 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | allow-prereleases: true 32 | 33 | - name: Get full python version 34 | id: full-python-version 35 | shell: bash 36 | run: | 37 | echo version=$(python -c "import sys; print('-'.join(map(str, sys.version_info)))") >> "$GITHUB_OUTPUT" 38 | 39 | - name: Install Poetry 40 | run: pipx install poetry 41 | 42 | - name: Configure Poetry 43 | shell: bash 44 | run: | 45 | poetry config virtualenvs.in-project true 46 | poetry env use python 47 | 48 | - name: Set up cache 49 | uses: actions/cache@v4 50 | id: cache 51 | with: 52 | path: .venv 53 | key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} 54 | 55 | - name: Ensure cache is healthy 56 | if: steps.cache.outputs.cache-hit == 'true' 57 | shell: bash 58 | run: timeout 10s poetry run pip --version || rm -rf .venv 59 | 60 | - name: Install dependencies 61 | shell: bash 62 | run: | 63 | poetry install 64 | 65 | - name: Run tests 66 | shell: bash 67 | run: | 68 | poetry run pytest -q tests 69 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/qa-array-inline-nested-1000.yaml: -------------------------------------------------------------------------------- 1 | key: 2 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [] 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [github_release]: https://img.shields.io/github/release/sdispater/tomlkit.svg?logo=github&logoColor=white 2 | [pypi_version]: https://img.shields.io/pypi/v/tomlkit.svg?logo=python&logoColor=white 3 | [python_versions]: https://img.shields.io/pypi/pyversions/tomlkit.svg?logo=python&logoColor=white 4 | [github_license]: https://img.shields.io/github/license/sdispater/tomlkit.svg?logo=github&logoColor=white 5 | [github_action]: https://github.com/sdispater/tomlkit/actions/workflows/tests.yml/badge.svg 6 | 7 | [![GitHub Release][github_release]](https://github.com/sdispater/tomlkit/releases/) 8 | [![PyPI Version][pypi_version]](https://pypi.org/project/tomlkit/) 9 | [![Python Versions][python_versions]](https://pypi.org/project/tomlkit/) 10 | [![License][github_license]](https://github.com/sdispater/tomlkit/blob/master/LICENSE) 11 |
12 | [![Tests][github_action]](https://github.com/sdispater/tomlkit/actions/workflows/tests.yml) 13 | 14 | # TOML Kit - Style-preserving TOML library for Python 15 | 16 | TOML Kit is a **1.0.0-compliant** [TOML](https://toml.io/) library. 17 | 18 | It includes a parser that preserves all comments, indentations, whitespace and internal element ordering, 19 | and makes them accessible and editable via an intuitive API. 20 | 21 | You can also create new TOML documents from scratch using the provided helpers. 22 | 23 | Part of the implementation has been adapted, improved and fixed from [Molten](https://github.com/LeopoldArkham/Molten). 24 | 25 | ## Usage 26 | 27 | See the [documentation](https://tomlkit.readthedocs.io/) for more information. 28 | 29 | ## Installation 30 | 31 | If you are using [Poetry](https://poetry.eustace.io), 32 | add `tomlkit` to your `pyproject.toml` file by using: 33 | 34 | ```bash 35 | poetry add tomlkit 36 | ``` 37 | 38 | If not, you can use `pip`: 39 | 40 | ```bash 41 | pip install tomlkit 42 | ``` 43 | 44 | ## Running tests 45 | 46 | Please clone the repo with submodules with the following command: 47 | ```bash 48 | git clone --recurse-submodules https://github.com/python-poetry/tomlkit.git 49 | ``` 50 | The `toml-test` submodule is required for running the tests. 51 | 52 | You can then run the tests with 53 | ```bash 54 | poetry run pytest -q tests 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | 17 | sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) 18 | 19 | from tomlkit import __version__ 20 | 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = "TOML Kit" 25 | copyright = "2021, Sébastien Eustace" 26 | author = "Sébastien Eustace" 27 | 28 | # The full version, including alpha/beta/rc tags 29 | release = __version__ 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 38 | "sphinx.ext.autodoc", 39 | ] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ["_templates"] 43 | 44 | # List of patterns, relative to source directory, that match files and 45 | # directories to ignore when looking for source files. 46 | # This pattern also affects html_static_path and html_extra_path. 47 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 48 | 49 | 50 | # -- Options for HTML output ------------------------------------------------- 51 | 52 | # The theme to use for HTML and HTML Help pages. See the documentation for 53 | # a list of builtin themes. 54 | # 55 | html_theme = "furo" 56 | 57 | # Add any paths that contain custom static files (such as style sheets) here, 58 | # relative to this directory. They are copied after the builtin static files, 59 | # so a file named "default.css" will overwrite the builtin "default.css". 60 | html_static_path = [] 61 | -------------------------------------------------------------------------------- /tests/examples/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "poetry" 3 | version = "0.11.2" 4 | description = "Python dependency management and packaging made easy." 5 | authors = [ 6 | "Sébastien Eustace " 7 | ] 8 | license = "MIT" 9 | 10 | readme = "README.md" 11 | 12 | homepage = "https://poetry.eustace.io/" 13 | repository = "https://github.com/sdispater/poet" 14 | documentation = "https://poetry.eustace.io/docs" 15 | 16 | keywords = ["packaging", "dependency", "poetry"] 17 | 18 | classifiers = [ 19 | "Topic :: Software Development :: Build Tools", 20 | "Topic :: Software Development :: Libraries :: Python Modules" 21 | ] 22 | 23 | # Requirements 24 | [tool.poetry.dependencies] 25 | python = "~2.7 || ^3.4" 26 | cleo = "^0.6.7" 27 | pytoml = "^0.1.16" 28 | requests = "^2.18" 29 | cachy = "^0.2" 30 | requests-toolbelt = "^0.8.0" 31 | jsonschema = "^2.6" 32 | pyrsistent = "^0.14.2" 33 | pyparsing = "^2.2" 34 | cachecontrol = { version = "^0.12.4", extras = ["filecache"] } 35 | pkginfo = "^1.4" 36 | html5lib = "^1.0" 37 | shellingham = "^1.1" 38 | 39 | # The typing module is not in the stdlib in Python 2.7 and 3.4 40 | typing = { version = "^3.6", python = "~2.7 || ~3.4" } 41 | 42 | # Use pathlib2 for Python 2.7 and 3.4 43 | pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" } 44 | # Use virtualenv for Python 2.7 since venv does not exist 45 | virtualenv = { version = "^16.0", python = "~2.7" } 46 | 47 | [tool.poetry.dev-dependencies] 48 | pytest = "^3.4" 49 | pytest-cov = "^2.5" 50 | mkdocs = "^0.17.3" 51 | pymdown-extensions = "^4.9" 52 | pygments = "^2.2" 53 | pytest-mock = "^1.9" 54 | pygments-github-lexers = "^0.0.5" 55 | black = { version = "^18.3-alpha.0", python = "^3.6" } 56 | pre-commit = "^1.10" 57 | tox = "^3.0" 58 | 59 | 60 | [tool.poetry.scripts] 61 | poetry = "poetry.console:main" 62 | 63 | 64 | [build-system] 65 | requires = ["poetry-core>=1.0.0a3"] 66 | build-backend = "poetry.core.masonry.api" 67 | 68 | 69 | [tool.black] 70 | line-length = 88 71 | py36 = true 72 | include = '\.pyi?$' 73 | exclude = ''' 74 | /( 75 | \.git 76 | | \.hg 77 | | \.mypy_cache 78 | | \.tox 79 | | \.venv 80 | | _build 81 | | build 82 | | dist 83 | | tests/toml-test 84 | )/ 85 | ''' 86 | 87 | [[tool.foo]] 88 | name = 'first' 89 | 90 | [[tool.foo]] 91 | name = 'second' 92 | 93 | # CS 94 | [[tool.foo]] 95 | name = 'third' 96 | 97 | [[tool.foo]] 98 | name = 'fourth' 99 | 100 | [[tool.bar]] 101 | foo = "bar" 102 | -------------------------------------------------------------------------------- /tomlkit/_types.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | from typing import Any 5 | from typing import TypeVar 6 | 7 | 8 | WT = TypeVar("WT", bound="WrapperType") 9 | 10 | if TYPE_CHECKING: # pragma: no cover 11 | # Define _CustomList and _CustomDict as a workaround for: 12 | # https://github.com/python/mypy/issues/11427 13 | # 14 | # According to this issue, the typeshed contains a "lie" 15 | # (it adds MutableSequence to the ancestry of list and MutableMapping to 16 | # the ancestry of dict) which completely messes with the type inference for 17 | # Table, InlineTable, Array and Container. 18 | # 19 | # Importing from builtins is preferred over simple assignment, see issues: 20 | # https://github.com/python/mypy/issues/8715 21 | # https://github.com/python/mypy/issues/10068 22 | from builtins import dict as _CustomDict 23 | from builtins import float as _CustomFloat 24 | from builtins import int as _CustomInt 25 | from builtins import list as _CustomList 26 | from typing import Callable 27 | from typing import Concatenate 28 | from typing import ParamSpec 29 | from typing import Protocol 30 | 31 | P = ParamSpec("P") 32 | 33 | class WrapperType(Protocol): 34 | def _new(self: WT, value: Any) -> WT: ... 35 | 36 | else: 37 | from collections.abc import MutableMapping 38 | from collections.abc import MutableSequence 39 | from numbers import Integral 40 | from numbers import Real 41 | 42 | class _CustomList(MutableSequence, list): 43 | """Adds MutableSequence mixin while pretending to be a builtin list""" 44 | 45 | def __add__(self, other): 46 | new_list = self.copy() 47 | new_list.extend(other) 48 | return new_list 49 | 50 | def __iadd__(self, other): 51 | self.extend(other) 52 | return self 53 | 54 | class _CustomDict(MutableMapping, dict): 55 | """Adds MutableMapping mixin while pretending to be a builtin dict""" 56 | 57 | def __or__(self, other): 58 | new_dict = self.copy() 59 | new_dict.update(other) 60 | return new_dict 61 | 62 | def __ior__(self, other): 63 | self.update(other) 64 | return self 65 | 66 | class _CustomInt(Integral, int): 67 | """Adds Integral mixin while pretending to be a builtin int""" 68 | 69 | class _CustomFloat(Real, float): 70 | """Adds Real mixin while pretending to be a builtin float""" 71 | 72 | 73 | def wrap_method( 74 | original_method: Callable[Concatenate[WT, P], Any], 75 | ) -> Callable[Concatenate[WT, P], Any]: 76 | def wrapper(self: WT, *args: P.args, **kwargs: P.kwargs) -> Any: 77 | result = original_method(self, *args, **kwargs) 78 | if result is NotImplemented: 79 | return result 80 | return self._new(result) 81 | 82 | return wrapper 83 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: Integration Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - "**" 10 | 11 | concurrency: 12 | group: integration-${{ github.head_ref || github.ref }} 13 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 14 | 15 | jobs: 16 | integration: 17 | name: Integration / ${{ matrix.os }} / ${{ matrix.python-version }} / ${{ matrix.project }} 18 | runs-on: ${{ matrix.image }} 19 | strategy: 20 | matrix: 21 | os: [ Ubuntu, macOS, Windows ] 22 | python-version: [ "3.11" ] 23 | project: [ "poetry", "poetry-core" ] 24 | include: 25 | - os: Ubuntu 26 | image: ubuntu-latest 27 | - os: Windows 28 | image: windows-latest 29 | - os: macOS 30 | image: macos-latest 31 | fail-fast: false 32 | defaults: 33 | run: 34 | shell: bash 35 | steps: 36 | - name: Checkout Source (${{ matrix.project }}) 37 | uses: actions/checkout@v4 38 | with: 39 | repository: python-poetry/${{ matrix.project }} 40 | 41 | - name: Checkout Source (tomlkit) 42 | uses: actions/checkout@v4 43 | with: 44 | path: tomlkit 45 | 46 | - uses: actions/setup-python@v5 47 | with: 48 | python-version: ${{ matrix.python-version }} 49 | 50 | - name: Bootstrap poetry 51 | run: pipx install poetry 52 | 53 | - name: Configure poetry 54 | run: | 55 | poetry config virtualenvs.in-project true 56 | poetry env use python 57 | 58 | - name: Get full Python version 59 | if: matrix.project != 'poetry-core' 60 | id: full-python-version 61 | run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") 62 | 63 | - name: Set up cache 64 | uses: actions/cache@v3 65 | if: matrix.project != 'poetry-core' 66 | id: cache 67 | with: 68 | path: ./.venv 69 | key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('poetry.lock') }} 70 | 71 | - name: Ensure cache is healthy 72 | if: steps.cache.outputs.cache-hit == 'true' 73 | run: timeout 10s poetry run pip --version || rm -rf .venv 74 | 75 | - name: Patch tomlkit dependency 76 | if: matrix.project != 'poetry-core' 77 | run: | 78 | # we explicitly remove to mitigate a bug in poetry 1.1.13, can be removed with 1.2.0 79 | poetry run pip uninstall -y tomlkit 80 | poetry add --lock ./tomlkit 81 | 82 | - name: Install dependencies 83 | run: poetry install 84 | 85 | - name: Remove poetry-core vendored tomlkit 86 | run: | 87 | rm -rf $(poetry run python -c "import poetry.core; print(poetry.core.__vendor_site__)")/tomlkit 88 | 89 | - name: Patch vendored tomlkit 90 | if: matrix.project == 'poetry-core' 91 | run: | 92 | cp -R ./tomlkit/tomlkit src/poetry/core/_vendor/. 93 | 94 | - name: Run tests 95 | run: poetry run pytest -q tests/ 96 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def example(): 8 | def _example(name): 9 | with open( 10 | os.path.join(os.path.dirname(__file__), "examples", name + ".toml"), 11 | encoding="utf-8", 12 | ) as f: 13 | return f.read() 14 | 15 | return _example 16 | 17 | 18 | @pytest.fixture 19 | def json_example(): 20 | def _example(name): 21 | with open( 22 | os.path.join(os.path.dirname(__file__), "examples", "json", name + ".json"), 23 | encoding="utf-8", 24 | ) as f: 25 | return f.read() 26 | 27 | return _example 28 | 29 | 30 | @pytest.fixture 31 | def invalid_example(): 32 | def _example(name): 33 | with open( 34 | os.path.join( 35 | os.path.dirname(__file__), "examples", "invalid", name + ".toml" 36 | ), 37 | encoding="utf-8", 38 | ) as f: 39 | return f.read() 40 | 41 | return _example 42 | 43 | 44 | TEST_DIR = os.path.join(os.path.dirname(__file__), "toml-test", "tests") 45 | IGNORED_TESTS = { 46 | "valid": [ 47 | "float/inf-and-nan", # Can't compare nan 48 | ] 49 | } 50 | 51 | 52 | def get_tomltest_cases(): 53 | dirs = sorted( 54 | f for f in os.listdir(TEST_DIR) if os.path.isdir(os.path.join(TEST_DIR, f)) 55 | ) 56 | assert dirs == ["invalid", "valid"] 57 | rv = {"invalid_encode": {}} 58 | for d in dirs: 59 | rv[d] = {} 60 | ignored = IGNORED_TESTS.get(d, []) 61 | 62 | for root, _, files in os.walk(os.path.join(TEST_DIR, d)): 63 | relpath = os.path.relpath(root, os.path.join(TEST_DIR, d)) 64 | if relpath == ".": 65 | relpath = "" 66 | for f in files: 67 | try: 68 | bn, ext = f.rsplit(".", 1) 69 | except ValueError: 70 | bn, ext = f.rsplit("-", 1) 71 | key = f"{relpath}/{bn}" 72 | if ext == "multi": 73 | continue 74 | if key in ignored: 75 | continue 76 | if d == "invalid" and relpath == "encoding": 77 | rv["invalid_encode"][bn] = os.path.join(root, f) 78 | continue 79 | if key not in rv[d]: 80 | rv[d][key] = {} 81 | with open(os.path.join(root, f), encoding="utf-8") as inp: 82 | rv[d][key][ext] = inp.read() 83 | return rv 84 | 85 | 86 | def pytest_generate_tests(metafunc): 87 | test_list = get_tomltest_cases() 88 | if "valid_case" in metafunc.fixturenames: 89 | metafunc.parametrize( 90 | "valid_case", 91 | test_list["valid"].values(), 92 | ids=list(test_list["valid"].keys()), 93 | ) 94 | elif "invalid_decode_case" in metafunc.fixturenames: 95 | metafunc.parametrize( 96 | "invalid_decode_case", 97 | test_list["invalid"].values(), 98 | ids=list(test_list["invalid"].keys()), 99 | ) 100 | elif "invalid_encode_case" in metafunc.fixturenames: 101 | metafunc.parametrize( 102 | "invalid_encode_case", 103 | test_list["invalid_encode"].values(), 104 | ids=list(test_list["invalid_encode"].keys()), 105 | ) 106 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | Parsing 5 | ------- 6 | 7 | TOML Kit comes with a fast and style-preserving parser to help you access 8 | the content of TOML files and strings:: 9 | 10 | 11 | >>> from tomlkit import dumps 12 | >>> from tomlkit import parse # you can also use loads 13 | 14 | >>> content = """[table] 15 | ... foo = "bar" # String 16 | ... """ 17 | >>> doc = parse(content) 18 | 19 | # doc is a TOMLDocument instance that holds all the information 20 | # about the TOML string. 21 | # It behaves like a standard dictionary. 22 | 23 | >>> assert doc["table"]["foo"] == "bar" 24 | 25 | # The string generated from the document is exactly the same 26 | # as the original string 27 | >>> assert dumps(doc) == content 28 | 29 | 30 | Modifying 31 | --------- 32 | 33 | TOML Kit provides an intuitive API to modify TOML documents:: 34 | 35 | >>> from tomlkit import dumps 36 | >>> from tomlkit import parse 37 | >>> from tomlkit import table 38 | 39 | >>> doc = parse("""[table] 40 | ... foo = "bar" # String 41 | ... """) 42 | 43 | >>> doc["table"]["baz"] = 13 44 | 45 | >>> dumps(doc) 46 | """[table] 47 | foo = "bar" # String 48 | baz = 13 49 | """ 50 | 51 | # Add a new table 52 | >>> tab = table() 53 | >>> tab.add("array", [1, 2, 3]) 54 | 55 | >>> doc["table2"] = tab 56 | 57 | >>> dumps(doc) 58 | """[table] 59 | foo = "bar" # String 60 | baz = 13 61 | 62 | [table2] 63 | array = [1, 2, 3] 64 | """ 65 | 66 | # Remove the newly added table 67 | >>> doc.pop("table2") 68 | # del doc["table2] is also possible 69 | 70 | Writing 71 | ------- 72 | 73 | You can also write a new TOML document from scratch. 74 | 75 | Let's say we want to create this following document 76 | 77 | .. code-block:: toml 78 | 79 | # This is a TOML document. 80 | 81 | title = "TOML Example" 82 | 83 | [owner] 84 | name = "Tom Preston-Werner" 85 | organization = "GitHub" 86 | bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." 87 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 88 | 89 | [database] 90 | server = "192.168.1.1" 91 | ports = [ 8001, 8001, 8002 ] 92 | connection_max = 5000 93 | enabled = true 94 | 95 | It can be created with the following code:: 96 | 97 | >>> from tomlkit import comment 98 | >>> from tomlkit import document 99 | >>> from tomlkit import nl 100 | >>> from tomlkit import table 101 | 102 | >>> doc = document() 103 | >>> doc.add(comment("This is a TOML document.")) 104 | >>> doc.add(nl()) 105 | >>> doc.add("title", "TOML Example") 106 | # Using doc["title"] = "TOML Example" is also possible 107 | 108 | >>> owner = table() 109 | >>> owner.add("name", "Tom Preston-Werner") 110 | >>> owner.add("organization", "GitHub") 111 | >>> owner.add("bio", "GitHub Cofounder & CEO\nLikes tater tots and beer.") 112 | >>> owner.add("dob", datetime(1979, 5, 27, 7, 32, tzinfo=utc)) 113 | >>> owner["dob"].comment("First class dates? Why not?") 114 | 115 | # Adding the table to the document 116 | >>> doc.add("owner", owner) 117 | 118 | >>> database = table() 119 | >>> database["server"] = "192.168.1.1" 120 | >>> database["ports"] = [8001, 8001, 8002] 121 | >>> database["connection_max"] = 5000 122 | >>> database["enabled"] = True 123 | 124 | >>> doc["database"] = database 125 | -------------------------------------------------------------------------------- /tests/test_toml_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from tomlkit.toml_document import TOMLDocument 4 | from tomlkit.toml_file import TOMLFile 5 | 6 | 7 | def test_toml_file(example): 8 | original_content = example("example") 9 | 10 | toml_file = os.path.join(os.path.dirname(__file__), "examples", "example.toml") 11 | toml = TOMLFile(toml_file) 12 | 13 | content = toml.read() 14 | assert isinstance(content, TOMLDocument) 15 | assert content["owner"]["organization"] == "GitHub" 16 | 17 | toml.write(content) 18 | 19 | try: 20 | with open(toml_file, encoding="utf-8") as f: 21 | assert original_content == f.read() 22 | finally: 23 | with open(toml_file, "w", encoding="utf-8", newline="") as f: 24 | assert f.write(original_content) 25 | 26 | 27 | def test_keep_old_eol(tmp_path): 28 | toml_path = tmp_path / "pyproject.toml" 29 | with open(toml_path, "wb+") as f: 30 | f.write(b"a = 1\r\nb = 2\r\n") 31 | 32 | f = TOMLFile(toml_path) 33 | content = f.read() 34 | content["b"] = 3 35 | f.write(content) 36 | 37 | with open(toml_path, "rb") as f: 38 | assert f.read() == b"a = 1\r\nb = 3\r\n" 39 | 40 | 41 | def test_keep_old_eol_2(tmp_path): 42 | toml_path = tmp_path / "pyproject.toml" 43 | with open(toml_path, "wb+") as f: 44 | f.write(b"a = 1\nb = 2\n") 45 | 46 | f = TOMLFile(toml_path) 47 | content = f.read() 48 | content["b"] = 3 49 | f.write(content) 50 | 51 | with open(toml_path, "rb") as f: 52 | assert f.read() == b"a = 1\nb = 3\n" 53 | 54 | 55 | def test_mixed_eol(tmp_path): 56 | toml_path = tmp_path / "pyproject.toml" 57 | with open(toml_path, "wb+") as f: 58 | f.write(b"a = 1\r\nrb = 2\n") 59 | 60 | f = TOMLFile(toml_path) 61 | f.write(f.read()) 62 | 63 | with open(toml_path, "rb") as f: 64 | assert f.read() == b"a = 1\r\nrb = 2\n" 65 | 66 | 67 | def test_consistent_eol(tmp_path): 68 | toml_path = tmp_path / "pyproject.toml" 69 | with open(toml_path, "wb+") as f: 70 | f.write(b"a = 1\r\nb = 2\r\n") 71 | 72 | f = TOMLFile(toml_path) 73 | content = f.read() 74 | content["c"] = 3 75 | f.write(content) 76 | 77 | with open(toml_path, "rb") as f: 78 | assert f.read() == b"a = 1\r\nb = 2\r\nc = 3\r\n" 79 | 80 | 81 | def test_consistent_eol_2(tmp_path): 82 | toml_path = tmp_path / "pyproject.toml" 83 | with open(toml_path, "wb+") as f: 84 | f.write(b"a = 1\nb = 2\n") 85 | 86 | f = TOMLFile(toml_path) 87 | content = f.read() 88 | content["c"] = 3 89 | content["c"].trivia.trail = "\r\n" 90 | f.write(content) 91 | 92 | with open(toml_path, "rb") as f: 93 | assert f.read() == b"a = 1\nb = 2\nc = 3\n" 94 | 95 | 96 | def test_default_eol_is_os_linesep(tmp_path): 97 | toml_path = tmp_path / "pyproject.toml" 98 | f = TOMLFile(toml_path) 99 | content = TOMLDocument() 100 | content.append("a", 1) 101 | content["a"].trivia.trail = "\n" 102 | content.append("b", 2) 103 | content["b"].trivia.trail = "\r\n" 104 | f.write(content) 105 | linesep = os.linesep.encode() 106 | with open(toml_path, "rb") as f: 107 | assert f.read() == b"a = 1" + linesep + b"b = 2" + linesep 108 | 109 | 110 | def test_readwrite_eol_windows(tmp_path): 111 | toml_path = tmp_path / "pyproject.toml" 112 | doc = TOMLDocument() 113 | doc.add("a", 1) 114 | f = TOMLFile(toml_path) 115 | f.write(doc) 116 | readback = f.read() 117 | assert doc.as_string() == readback.as_string() 118 | -------------------------------------------------------------------------------- /tests/test_build.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from tomlkit import aot 4 | from tomlkit import array 5 | from tomlkit import comment 6 | from tomlkit import document 7 | from tomlkit import item 8 | from tomlkit import nl 9 | from tomlkit import parse 10 | from tomlkit import table 11 | from tomlkit._utils import _utc 12 | 13 | 14 | def test_build_example(example): 15 | content = example("example") 16 | 17 | doc = document() 18 | doc.add(comment("This is a TOML document. Boom.")) 19 | doc.add(nl()) 20 | doc.add("title", "TOML Example") 21 | 22 | owner = table() 23 | owner.add("name", "Tom Preston-Werner") 24 | owner.add("organization", "GitHub") 25 | owner.add("bio", "GitHub Cofounder & CEO\nLikes tater tots and beer.") 26 | owner.add("dob", datetime.datetime(1979, 5, 27, 7, 32, tzinfo=_utc)) 27 | owner["dob"].comment("First class dates? Why not?") 28 | 29 | doc.add("owner", owner) 30 | 31 | database = table() 32 | database["server"] = "192.168.1.1" 33 | database["ports"] = [8001, 8001, 8002] 34 | database["connection_max"] = 5000 35 | database["enabled"] = True 36 | 37 | doc["database"] = database 38 | 39 | servers = table() 40 | servers.add(nl()) 41 | c = comment( 42 | "You can indent as you please. Tabs or spaces. TOML don't care." 43 | ).indent(2) 44 | c.trivia.trail = "" 45 | servers.add(c) 46 | alpha = table() 47 | servers.append("alpha", alpha) 48 | alpha.indent(2) 49 | alpha.add("ip", "10.0.0.1") 50 | alpha.add("dc", "eqdc10") 51 | 52 | beta = table() 53 | servers.append("beta", beta) 54 | beta.add("ip", "10.0.0.2") 55 | beta.add("dc", "eqdc10") 56 | beta.add("country", "中国") 57 | beta["country"].comment("This should be parsed as UTF-8") 58 | beta.indent(2) 59 | 60 | doc["servers"] = servers 61 | 62 | clients = table() 63 | doc.add("clients", clients) 64 | clients["data"] = item([["gamma", "delta"], [1, 2]]).comment( 65 | "just an update to make sure parsers support it" 66 | ) 67 | 68 | clients.add(nl()) 69 | clients.add(comment("Line breaks are OK when inside arrays")) 70 | clients["hosts"] = array( 71 | """[ 72 | "alpha", 73 | "omega" 74 | ]""" 75 | ) 76 | 77 | doc.add(nl()) 78 | doc.add(comment("Products")) 79 | 80 | products = aot() 81 | doc["products"] = products 82 | 83 | hammer = table().indent(2) 84 | hammer["name"] = "Hammer" 85 | hammer["sku"] = 738594937 86 | 87 | nail = table().indent(2) 88 | nail["name"] = "Nail" 89 | nail["sku"] = 284758393 90 | nail["color"] = "gray" 91 | 92 | products.append(hammer) 93 | products.append(nail) 94 | 95 | assert content == doc.as_string() 96 | 97 | 98 | def test_add_remove(): 99 | content = "" 100 | 101 | doc = parse(content) 102 | doc.append("foo", "bar") 103 | 104 | assert ( 105 | doc.as_string() 106 | == """foo = "bar" 107 | """ 108 | ) 109 | 110 | doc.remove("foo") 111 | 112 | assert doc.as_string() == "" 113 | 114 | 115 | def test_append_table_after_multiple_indices(): 116 | content = """ 117 | [packages] 118 | foo = "*" 119 | 120 | [settings] 121 | enable = false 122 | 123 | [packages.bar] 124 | version = "*" 125 | """ 126 | doc = parse(content) 127 | doc.append("foobar", {"name": "John"}) 128 | 129 | 130 | def test_top_level_keys_are_put_at_the_root_of_the_document(): 131 | doc = document() 132 | doc.add(comment("Comment")) 133 | doc["foo"] = {"name": "test"} 134 | doc["bar"] = 1 135 | 136 | expected = """\ 137 | # Comment 138 | bar = 1 139 | 140 | [foo] 141 | name = "test" 142 | """ 143 | 144 | assert doc.as_string() == expected 145 | -------------------------------------------------------------------------------- /tests/examples/json/pyproject.json: -------------------------------------------------------------------------------- 1 | { 2 | "tool": { 3 | "poetry": { 4 | "name": "poetry", 5 | "version": "0.11.2", 6 | "description": "Python dependency management and packaging made easy.", 7 | "authors": [ 8 | "S\u00e9bastien Eustace " 9 | ], 10 | "license": "MIT", 11 | "readme": "README.md", 12 | "homepage": "https://poetry.eustace.io/", 13 | "repository": "https://github.com/sdispater/poet", 14 | "documentation": "https://poetry.eustace.io/docs", 15 | "keywords": [ 16 | "packaging", 17 | "dependency", 18 | "poetry" 19 | ], 20 | "classifiers": [ 21 | "Topic :: Software Development :: Build Tools", 22 | "Topic :: Software Development :: Libraries :: Python Modules" 23 | ], 24 | "dependencies": { 25 | "python": "~2.7 || ^3.4", 26 | "cleo": "^0.6.7", 27 | "pytoml": "^0.1.16", 28 | "requests": "^2.18", 29 | "cachy": "^0.2", 30 | "requests-toolbelt": "^0.8.0", 31 | "jsonschema": "^2.6", 32 | "pyrsistent": "^0.14.2", 33 | "pyparsing": "^2.2", 34 | "cachecontrol": { 35 | "version": "^0.12.4", 36 | "extras": [ 37 | "filecache" 38 | ] 39 | }, 40 | "pkginfo": "^1.4", 41 | "html5lib": "^1.0", 42 | "shellingham": "^1.1", 43 | "typing": { 44 | "version": "^3.6", 45 | "python": "~2.7 || ~3.4" 46 | }, 47 | "pathlib2": { 48 | "version": "^2.3", 49 | "python": "~2.7 || ~3.4" 50 | }, 51 | "virtualenv": { 52 | "version": "^16.0", 53 | "python": "~2.7" 54 | } 55 | }, 56 | "dev-dependencies": { 57 | "pytest": "^3.4", 58 | "pytest-cov": "^2.5", 59 | "mkdocs": "^0.17.3", 60 | "pymdown-extensions": "^4.9", 61 | "pygments": "^2.2", 62 | "pytest-mock": "^1.9", 63 | "pygments-github-lexers": "^0.0.5", 64 | "black": { 65 | "version": "^18.3-alpha.0", 66 | "python": "^3.6" 67 | }, 68 | "pre-commit": "^1.10", 69 | "tox": "^3.0" 70 | }, 71 | "scripts": { 72 | "poetry": "poetry.console:main" 73 | } 74 | }, 75 | "black": { 76 | "line-length": 88, 77 | "py36": true, 78 | "include": "\\.pyi?$", 79 | "exclude": "/(\n \\.git\n | \\.hg\n | \\.mypy_cache\n | \\.tox\n | \\.venv\n | _build\n | build\n | dist\n | tests/toml-test\n)/\n" 80 | }, 81 | "foo": [ 82 | { 83 | "name": "first" 84 | }, 85 | { 86 | "name": "second" 87 | }, 88 | { 89 | "name": "third" 90 | }, 91 | { 92 | "name": "fourth" 93 | } 94 | ], 95 | "bar": [ 96 | { 97 | "foo": "bar" 98 | } 99 | ] 100 | }, 101 | "build-system": { 102 | "requires": [ 103 | "poetry-core>=1.0.0a3" 104 | ], 105 | "build-backend": "poetry.core.masonry.api" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/test_toml_spec_tests.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | 5 | import pytest 6 | import yaml 7 | 8 | from tomlkit import parse 9 | from tomlkit._compat import decode 10 | from tomlkit._utils import parse_rfc3339 11 | from tomlkit.exceptions import TOMLKitError 12 | 13 | 14 | IGNORED_TESTS = [] 15 | # The following tests trigger a RecursionError 16 | IGNORED_TESTS += ["qa-array-inline-nested-1000", "qa-table-inline-nested-1000"] 17 | # The following tests don't work due to time microseconds precision of the tests 18 | IGNORED_TESTS += ["spec-date-time-6", "spec-date-time-local-2", "spec-time-2"] 19 | # The following tests don't work due to nan always comparing to False 20 | IGNORED_TESTS += ["spec-float-13", "spec-float-14", "spec-float-15"] 21 | # The following tests don't work due to issues with th epyyaml library 22 | IGNORED_TESTS += ["spec-key-value-pair-9"] 23 | SPEC_TEST_DIR = os.path.join(os.path.dirname(__file__), "toml-spec-tests") 24 | VALID_TESTS = sorted( 25 | os.path.basename(f).rsplit(".", 1)[0] 26 | for f in os.listdir(os.path.join(SPEC_TEST_DIR, "values")) 27 | if os.path.basename(f).rsplit(".", 1)[0] not in IGNORED_TESTS 28 | ) 29 | ERROR_TESTS = sorted( 30 | os.path.basename(f).rsplit(".", 1)[0] 31 | for f in os.listdir(os.path.join(SPEC_TEST_DIR, "errors")) 32 | if os.path.basename(f).rsplit(".", 1)[0] not in IGNORED_TESTS 33 | ) 34 | 35 | 36 | def to_bool(s): 37 | assert s in ["true", "false"] 38 | 39 | return s == "true" 40 | 41 | 42 | stypes = { 43 | "string": str, 44 | "bool": to_bool, 45 | "integer": int, 46 | "float": float, 47 | "datetime": parse_rfc3339, 48 | "datetime-local": parse_rfc3339, 49 | "date": parse_rfc3339, 50 | "time": parse_rfc3339, 51 | } 52 | 53 | loader = yaml.SafeLoader 54 | loader.add_implicit_resolver( 55 | "tag:yaml.org,2002:float", 56 | re.compile( 57 | """^(?: 58 | [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)? 59 | |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+) 60 | |\\.[0-9_]+(?:[eE][-+][0-9]+)? 61 | |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]* 62 | |[-+]?\\.(?:inf|Inf|INF) 63 | |\\.(?:nan|NaN|NAN))$""", 64 | re.X, 65 | ), 66 | list("-+0123456789."), 67 | ) 68 | 69 | 70 | def untag(value): 71 | if isinstance(value, list): 72 | return [untag(i) for i in value] 73 | elif "type" in value and "value" in value and len(value) == 2: 74 | if value["type"] in stypes: 75 | val = decode(value["value"]) 76 | 77 | return stypes[value["type"]](val) 78 | elif value["type"] == "array": 79 | return [untag(i) for i in value["value"]] 80 | else: 81 | raise Exception(f"Unsupported type {value['type']}") 82 | else: 83 | return {k: untag(v) for k, v in value.items()} 84 | 85 | 86 | @pytest.mark.parametrize("test", VALID_TESTS) 87 | def test_valid_decode(test): 88 | toml_file = os.path.join(SPEC_TEST_DIR, "values", test + ".toml") 89 | yaml_file = os.path.join(SPEC_TEST_DIR, "values", test + ".yaml") 90 | with open(toml_file, encoding="utf-8") as f: 91 | toml_content = f.read() 92 | toml_val = parse(toml_content) 93 | 94 | if os.path.exists(yaml_file): 95 | with open(yaml_file, encoding="utf-8") as f: 96 | yaml_val = yaml.load(f.read(), Loader=loader) 97 | else: 98 | with open( 99 | os.path.join(SPEC_TEST_DIR, "values", test + ".json"), encoding="utf-8" 100 | ) as f: 101 | yaml_val = untag(json.loads(f.read())) 102 | 103 | assert toml_val == yaml_val 104 | assert toml_val.as_string() == toml_content 105 | 106 | 107 | @pytest.mark.parametrize("test", ERROR_TESTS) 108 | def test_invalid_decode(test): 109 | toml_file = os.path.join(SPEC_TEST_DIR, "errors", test + ".toml") 110 | with pytest.raises(TOMLKitError), open(toml_file, encoding="utf-8") as f: 111 | parse(f.read()) 112 | -------------------------------------------------------------------------------- /tomlkit/_utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | 5 | from collections.abc import Mapping 6 | from datetime import date 7 | from datetime import datetime 8 | from datetime import time 9 | from datetime import timedelta 10 | from datetime import timezone 11 | from typing import Collection 12 | 13 | from tomlkit._compat import decode 14 | 15 | 16 | RFC_3339_LOOSE = re.compile( 17 | "^" 18 | r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date 19 | "(" 20 | "([Tt ])?" # Separator 21 | r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?" # Time 22 | r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone 23 | ")?" 24 | "$" 25 | ) 26 | 27 | RFC_3339_DATETIME = re.compile( 28 | "^" 29 | "([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date 30 | "[Tt ]" # Separator 31 | r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time 32 | r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone 33 | "$" 34 | ) 35 | 36 | RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$") 37 | 38 | RFC_3339_TIME = re.compile( 39 | r"^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$" 40 | ) 41 | 42 | _utc = timezone(timedelta(), "UTC") 43 | 44 | 45 | def parse_rfc3339(string: str) -> datetime | date | time: 46 | m = RFC_3339_DATETIME.match(string) 47 | if m: 48 | year = int(m.group(1)) 49 | month = int(m.group(2)) 50 | day = int(m.group(3)) 51 | hour = int(m.group(4)) 52 | minute = int(m.group(5)) 53 | second = int(m.group(6)) 54 | microsecond = 0 55 | 56 | if m.group(7): 57 | microsecond = int((f"{m.group(8):<06s}")[:6]) 58 | 59 | if m.group(9): 60 | # Timezone 61 | tz = m.group(9) 62 | if tz.upper() == "Z": 63 | tzinfo = _utc 64 | else: 65 | sign = m.group(11)[0] 66 | hour_offset, minute_offset = int(m.group(12)), int(m.group(13)) 67 | offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60) 68 | if sign == "-": 69 | offset = -offset 70 | 71 | tzinfo = timezone(offset, f"{sign}{m.group(12)}:{m.group(13)}") 72 | 73 | return datetime( 74 | year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo 75 | ) 76 | else: 77 | return datetime(year, month, day, hour, minute, second, microsecond) 78 | 79 | m = RFC_3339_DATE.match(string) 80 | if m: 81 | year = int(m.group(1)) 82 | month = int(m.group(2)) 83 | day = int(m.group(3)) 84 | 85 | return date(year, month, day) 86 | 87 | m = RFC_3339_TIME.match(string) 88 | if m: 89 | hour = int(m.group(1)) 90 | minute = int(m.group(2)) 91 | second = int(m.group(3)) 92 | microsecond = 0 93 | 94 | if m.group(4): 95 | microsecond = int((f"{m.group(5):<06s}")[:6]) 96 | 97 | return time(hour, minute, second, microsecond) 98 | 99 | raise ValueError("Invalid RFC 339 string") 100 | 101 | 102 | # https://toml.io/en/v1.0.0#string 103 | CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)} 104 | _escaped = { 105 | "b": "\b", 106 | "t": "\t", 107 | "n": "\n", 108 | "f": "\f", 109 | "r": "\r", 110 | '"': '"', 111 | "\\": "\\", 112 | } 113 | _compact_escapes = { 114 | **{v: f"\\{k}" for k, v in _escaped.items()}, 115 | '"""': '""\\"', 116 | } 117 | _basic_escapes = CONTROL_CHARS | {'"', "\\"} 118 | 119 | 120 | def _unicode_escape(seq: str) -> str: 121 | return "".join(f"\\u{ord(c):04x}" for c in seq) 122 | 123 | 124 | def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str: 125 | s = decode(s) 126 | 127 | res = [] 128 | start = 0 129 | 130 | def flush(inc=1): 131 | if start != i: 132 | res.append(s[start:i]) 133 | 134 | return i + inc 135 | 136 | found_sequences = {seq for seq in escape_sequences if seq in s} 137 | 138 | i = 0 139 | while i < len(s): 140 | for seq in found_sequences: 141 | seq_len = len(seq) 142 | if s[i:].startswith(seq): 143 | start = flush(seq_len) 144 | res.append(_compact_escapes.get(seq) or _unicode_escape(seq)) 145 | i += seq_len - 1 # fast-forward escape sequence 146 | i += 1 147 | 148 | flush() 149 | 150 | return "".join(res) 151 | 152 | 153 | def merge_dicts(d1: dict, d2: dict) -> dict: 154 | for k, v in d2.items(): 155 | if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping): 156 | merge_dicts(d1[k], v) 157 | else: 158 | d1[k] = d2[k] 159 | -------------------------------------------------------------------------------- /tomlkit/source.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from copy import copy 4 | from typing import Any 5 | 6 | from tomlkit.exceptions import ParseError 7 | from tomlkit.exceptions import UnexpectedCharError 8 | from tomlkit.toml_char import TOMLChar 9 | 10 | 11 | class _State: 12 | def __init__( 13 | self, 14 | source: Source, 15 | save_marker: bool | None = False, 16 | restore: bool | None = False, 17 | ) -> None: 18 | self._source = source 19 | self._save_marker = save_marker 20 | self.restore = restore 21 | 22 | def __enter__(self) -> _State: 23 | # Entering this context manager - save the state 24 | self._chars = copy(self._source._chars) 25 | self._idx = self._source._idx 26 | self._current = self._source._current 27 | self._marker = self._source._marker 28 | 29 | return self 30 | 31 | def __exit__(self, exception_type, exception_val, trace): 32 | # Exiting this context manager - restore the prior state 33 | if self.restore or exception_type: 34 | self._source._chars = self._chars 35 | self._source._idx = self._idx 36 | self._source._current = self._current 37 | if self._save_marker: 38 | self._source._marker = self._marker 39 | 40 | 41 | class _StateHandler: 42 | """ 43 | State preserver for the Parser. 44 | """ 45 | 46 | def __init__(self, source: Source) -> None: 47 | self._source = source 48 | self._states = [] 49 | 50 | def __call__(self, *args, **kwargs): 51 | return _State(self._source, *args, **kwargs) 52 | 53 | def __enter__(self) -> _State: 54 | state = self() 55 | self._states.append(state) 56 | return state.__enter__() 57 | 58 | def __exit__(self, exception_type, exception_val, trace): 59 | state = self._states.pop() 60 | return state.__exit__(exception_type, exception_val, trace) 61 | 62 | 63 | class Source(str): 64 | EOF = TOMLChar("\0") 65 | 66 | def __init__(self, _: str) -> None: 67 | super().__init__() 68 | 69 | # Collection of TOMLChars 70 | self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self)]) 71 | 72 | self._idx = 0 73 | self._marker = 0 74 | self._current = TOMLChar("") 75 | 76 | self._state = _StateHandler(self) 77 | 78 | self.inc() 79 | 80 | def reset(self): 81 | # initialize both idx and current 82 | self.inc() 83 | 84 | # reset marker 85 | self.mark() 86 | 87 | @property 88 | def state(self) -> _StateHandler: 89 | return self._state 90 | 91 | @property 92 | def idx(self) -> int: 93 | return self._idx 94 | 95 | @property 96 | def current(self) -> TOMLChar: 97 | return self._current 98 | 99 | @property 100 | def marker(self) -> int: 101 | return self._marker 102 | 103 | def extract(self) -> str: 104 | """ 105 | Extracts the value between marker and index 106 | """ 107 | return self[self._marker : self._idx] 108 | 109 | def inc(self, exception: type[ParseError] | None = None) -> bool: 110 | """ 111 | Increments the parser if the end of the input has not been reached. 112 | Returns whether or not it was able to advance. 113 | """ 114 | try: 115 | self._idx, self._current = next(self._chars) 116 | 117 | return True 118 | except StopIteration: 119 | self._idx = len(self) 120 | self._current = self.EOF 121 | if exception: 122 | raise self.parse_error(exception) from None 123 | 124 | return False 125 | 126 | def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool: 127 | """ 128 | Increments the parser by n characters 129 | if the end of the input has not been reached. 130 | """ 131 | return all(self.inc(exception=exception) for _ in range(n)) 132 | 133 | def consume(self, chars, min=0, max=-1): 134 | """ 135 | Consume chars until min/max is satisfied is valid. 136 | """ 137 | while self.current in chars and max != 0: 138 | min -= 1 139 | max -= 1 140 | if not self.inc(): 141 | break 142 | 143 | # failed to consume minimum number of characters 144 | if min > 0: 145 | raise self.parse_error(UnexpectedCharError, self.current) 146 | 147 | def end(self) -> bool: 148 | """ 149 | Returns True if the parser has reached the end of the input. 150 | """ 151 | return self._current is self.EOF 152 | 153 | def mark(self) -> None: 154 | """ 155 | Sets the marker to the index's current position 156 | """ 157 | self._marker = self._idx 158 | 159 | def parse_error( 160 | self, 161 | exception: type[ParseError] = ParseError, 162 | *args: Any, 163 | **kwargs: Any, 164 | ) -> ParseError: 165 | """ 166 | Creates a generic "parse error" at the current position. 167 | """ 168 | line, col = self._to_linecol() 169 | 170 | return exception(line, col, *args, **kwargs) 171 | 172 | def _to_linecol(self) -> tuple[int, int]: 173 | cur = 0 174 | for i, line in enumerate(self.splitlines()): 175 | if cur + len(line) + 1 > self.idx: 176 | return (i + 1, self.idx - cur) 177 | 178 | cur += len(line) + 1 179 | 180 | return len(self.splitlines()), 0 181 | -------------------------------------------------------------------------------- /tomlkit/exceptions.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Collection 4 | 5 | 6 | class TOMLKitError(Exception): 7 | pass 8 | 9 | 10 | class ParseError(ValueError, TOMLKitError): 11 | """ 12 | This error occurs when the parser encounters a syntax error 13 | in the TOML being parsed. The error references the line and 14 | location within the line where the error was encountered. 15 | """ 16 | 17 | def __init__(self, line: int, col: int, message: str | None = None) -> None: 18 | self._line = line 19 | self._col = col 20 | 21 | if message is None: 22 | message = "TOML parse error" 23 | 24 | super().__init__(f"{message} at line {self._line} col {self._col}") 25 | 26 | @property 27 | def line(self): 28 | return self._line 29 | 30 | @property 31 | def col(self): 32 | return self._col 33 | 34 | 35 | class MixedArrayTypesError(ParseError): 36 | """ 37 | An array was found that had two or more element types. 38 | """ 39 | 40 | def __init__(self, line: int, col: int) -> None: 41 | message = "Mixed types found in array" 42 | 43 | super().__init__(line, col, message=message) 44 | 45 | 46 | class InvalidNumberError(ParseError): 47 | """ 48 | A numeric field was improperly specified. 49 | """ 50 | 51 | def __init__(self, line: int, col: int) -> None: 52 | message = "Invalid number" 53 | 54 | super().__init__(line, col, message=message) 55 | 56 | 57 | class InvalidDateTimeError(ParseError): 58 | """ 59 | A datetime field was improperly specified. 60 | """ 61 | 62 | def __init__(self, line: int, col: int) -> None: 63 | message = "Invalid datetime" 64 | 65 | super().__init__(line, col, message=message) 66 | 67 | 68 | class InvalidDateError(ParseError): 69 | """ 70 | A date field was improperly specified. 71 | """ 72 | 73 | def __init__(self, line: int, col: int) -> None: 74 | message = "Invalid date" 75 | 76 | super().__init__(line, col, message=message) 77 | 78 | 79 | class InvalidTimeError(ParseError): 80 | """ 81 | A date field was improperly specified. 82 | """ 83 | 84 | def __init__(self, line: int, col: int) -> None: 85 | message = "Invalid time" 86 | 87 | super().__init__(line, col, message=message) 88 | 89 | 90 | class InvalidNumberOrDateError(ParseError): 91 | """ 92 | A numeric or date field was improperly specified. 93 | """ 94 | 95 | def __init__(self, line: int, col: int) -> None: 96 | message = "Invalid number or date format" 97 | 98 | super().__init__(line, col, message=message) 99 | 100 | 101 | class InvalidUnicodeValueError(ParseError): 102 | """ 103 | A unicode code was improperly specified. 104 | """ 105 | 106 | def __init__(self, line: int, col: int) -> None: 107 | message = "Invalid unicode value" 108 | 109 | super().__init__(line, col, message=message) 110 | 111 | 112 | class UnexpectedCharError(ParseError): 113 | """ 114 | An unexpected character was found during parsing. 115 | """ 116 | 117 | def __init__(self, line: int, col: int, char: str) -> None: 118 | message = f"Unexpected character: {char!r}" 119 | 120 | super().__init__(line, col, message=message) 121 | 122 | 123 | class EmptyKeyError(ParseError): 124 | """ 125 | An empty key was found during parsing. 126 | """ 127 | 128 | def __init__(self, line: int, col: int) -> None: 129 | message = "Empty key" 130 | 131 | super().__init__(line, col, message=message) 132 | 133 | 134 | class EmptyTableNameError(ParseError): 135 | """ 136 | An empty table name was found during parsing. 137 | """ 138 | 139 | def __init__(self, line: int, col: int) -> None: 140 | message = "Empty table name" 141 | 142 | super().__init__(line, col, message=message) 143 | 144 | 145 | class InvalidCharInStringError(ParseError): 146 | """ 147 | The string being parsed contains an invalid character. 148 | """ 149 | 150 | def __init__(self, line: int, col: int, char: str) -> None: 151 | message = f"Invalid character {char!r} in string" 152 | 153 | super().__init__(line, col, message=message) 154 | 155 | 156 | class UnexpectedEofError(ParseError): 157 | """ 158 | The TOML being parsed ended before the end of a statement. 159 | """ 160 | 161 | def __init__(self, line: int, col: int) -> None: 162 | message = "Unexpected end of file" 163 | 164 | super().__init__(line, col, message=message) 165 | 166 | 167 | class InternalParserError(ParseError): 168 | """ 169 | An error that indicates a bug in the parser. 170 | """ 171 | 172 | def __init__(self, line: int, col: int, message: str | None = None) -> None: 173 | msg = "Internal parser error" 174 | if message: 175 | msg += f" ({message})" 176 | 177 | super().__init__(line, col, message=msg) 178 | 179 | 180 | class NonExistentKey(KeyError, TOMLKitError): 181 | """ 182 | A non-existent key was used. 183 | """ 184 | 185 | def __init__(self, key): 186 | message = f'Key "{key}" does not exist.' 187 | 188 | super().__init__(message) 189 | 190 | 191 | class KeyAlreadyPresent(TOMLKitError): 192 | """ 193 | An already present key was used. 194 | """ 195 | 196 | def __init__(self, key): 197 | key = getattr(key, "key", key) 198 | message = f'Key "{key}" already exists.' 199 | 200 | super().__init__(message) 201 | 202 | 203 | class InvalidControlChar(ParseError): 204 | def __init__(self, line: int, col: int, char: int, type: str) -> None: 205 | display_code = "\\u00" 206 | 207 | if char < 16: 208 | display_code += "0" 209 | 210 | display_code += hex(char)[2:] 211 | 212 | message = ( 213 | "Control characters (codes less than 0x1f and 0x7f)" 214 | f" are not allowed in {type}, " 215 | f"use {display_code} instead" 216 | ) 217 | 218 | super().__init__(line, col, message=message) 219 | 220 | 221 | class InvalidStringError(ValueError, TOMLKitError): 222 | def __init__(self, value: str, invalid_sequences: Collection[str], delimiter: str): 223 | repr_ = repr(value)[1:-1] 224 | super().__init__( 225 | f"Invalid string: {delimiter}{repr_}{delimiter}. " 226 | f"The character sequences {invalid_sequences} are invalid." 227 | ) 228 | 229 | 230 | class ConvertError(TypeError, ValueError, TOMLKitError): 231 | """Raised when item() fails to convert a value. 232 | It should be a TypeError, but due to historical reasons 233 | it needs to subclass ValueError as well. 234 | """ 235 | -------------------------------------------------------------------------------- /tests/toml-spec-tests/values/qa-table-inline-nested-1000.toml: -------------------------------------------------------------------------------- 1 | key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {key = {}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} } 2 | -------------------------------------------------------------------------------- /tomlkit/api.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import contextlib 4 | import datetime as _datetime 5 | 6 | from collections.abc import Mapping 7 | from typing import IO 8 | from typing import TYPE_CHECKING 9 | from typing import Iterable 10 | from typing import TypeVar 11 | 12 | from tomlkit._utils import parse_rfc3339 13 | from tomlkit.container import Container 14 | from tomlkit.exceptions import UnexpectedCharError 15 | from tomlkit.items import CUSTOM_ENCODERS 16 | from tomlkit.items import AoT 17 | from tomlkit.items import Array 18 | from tomlkit.items import Bool 19 | from tomlkit.items import Comment 20 | from tomlkit.items import Date 21 | from tomlkit.items import DateTime 22 | from tomlkit.items import DottedKey 23 | from tomlkit.items import Float 24 | from tomlkit.items import InlineTable 25 | from tomlkit.items import Integer 26 | from tomlkit.items import Item as _Item 27 | from tomlkit.items import Key 28 | from tomlkit.items import SingleKey 29 | from tomlkit.items import String 30 | from tomlkit.items import StringType as _StringType 31 | from tomlkit.items import Table 32 | from tomlkit.items import Time 33 | from tomlkit.items import Trivia 34 | from tomlkit.items import Whitespace 35 | from tomlkit.items import item 36 | from tomlkit.parser import Parser 37 | from tomlkit.toml_document import TOMLDocument 38 | 39 | 40 | if TYPE_CHECKING: 41 | from tomlkit.items import Encoder 42 | 43 | E = TypeVar("E", bound=Encoder) 44 | 45 | 46 | def loads(string: str | bytes) -> TOMLDocument: 47 | """ 48 | Parses a string into a TOMLDocument. 49 | 50 | Alias for parse(). 51 | """ 52 | return parse(string) 53 | 54 | 55 | def dumps(data: Mapping, sort_keys: bool = False) -> str: 56 | """ 57 | Dumps a TOMLDocument into a string. 58 | """ 59 | if not isinstance(data, (Table, InlineTable, Container)) and isinstance( 60 | data, Mapping 61 | ): 62 | data = item(dict(data), _sort_keys=sort_keys) 63 | 64 | try: 65 | # data should be a `Container` (and therefore implement `as_string`) 66 | # for all type safe invocations of this function 67 | return data.as_string() # type: ignore[attr-defined] 68 | except AttributeError as ex: 69 | msg = f"Expecting Mapping or TOML Table or Container, {type(data)} given" 70 | raise TypeError(msg) from ex 71 | 72 | 73 | def load(fp: IO[str] | IO[bytes]) -> TOMLDocument: 74 | """ 75 | Load toml document from a file-like object. 76 | """ 77 | return parse(fp.read()) 78 | 79 | 80 | def dump(data: Mapping, fp: IO[str], *, sort_keys: bool = False) -> None: 81 | """ 82 | Dump a TOMLDocument into a writable file stream. 83 | 84 | :param data: a dict-like object to dump 85 | :param sort_keys: if true, sort the keys in alphabetic order 86 | 87 | :Example: 88 | 89 | >>> with open("output.toml", "w") as fp: 90 | ... tomlkit.dump(data, fp) 91 | """ 92 | fp.write(dumps(data, sort_keys=sort_keys)) 93 | 94 | 95 | def parse(string: str | bytes) -> TOMLDocument: 96 | """ 97 | Parses a string or bytes into a TOMLDocument. 98 | """ 99 | return Parser(string).parse() 100 | 101 | 102 | def document() -> TOMLDocument: 103 | """ 104 | Returns a new TOMLDocument instance. 105 | """ 106 | return TOMLDocument() 107 | 108 | 109 | # Items 110 | def integer(raw: str | int) -> Integer: 111 | """Create an integer item from a number or string.""" 112 | return item(int(raw)) 113 | 114 | 115 | def float_(raw: str | float) -> Float: 116 | """Create an float item from a number or string.""" 117 | return item(float(raw)) 118 | 119 | 120 | def boolean(raw: str | bool) -> Bool: 121 | """Turn `true` or `false` into a boolean item.""" 122 | return item(raw == "true" if isinstance(raw, str) else raw) 123 | 124 | 125 | def string( 126 | raw: str, 127 | *, 128 | literal: bool = False, 129 | multiline: bool = False, 130 | escape: bool = True, 131 | ) -> String: 132 | """Create a string item. 133 | 134 | By default, this function will create *single line basic* strings, but 135 | boolean flags (e.g. ``literal=True`` and/or ``multiline=True``) 136 | can be used for personalization. 137 | 138 | For more information, please check the spec: ``__. 139 | 140 | Common escaping rules will be applied for basic strings. 141 | This can be controlled by explicitly setting ``escape=False``. 142 | Please note that, if you disable escaping, you will have to make sure that 143 | the given strings don't contain any forbidden character or sequence. 144 | """ 145 | type_ = _StringType.select(literal, multiline) 146 | return String.from_raw(raw, type_, escape) 147 | 148 | 149 | def date(raw: str) -> Date: 150 | """Create a TOML date.""" 151 | value = parse_rfc3339(raw) 152 | if not isinstance(value, _datetime.date): 153 | raise ValueError("date() only accepts date strings.") 154 | 155 | return item(value) 156 | 157 | 158 | def time(raw: str) -> Time: 159 | """Create a TOML time.""" 160 | value = parse_rfc3339(raw) 161 | if not isinstance(value, _datetime.time): 162 | raise ValueError("time() only accepts time strings.") 163 | 164 | return item(value) 165 | 166 | 167 | def datetime(raw: str) -> DateTime: 168 | """Create a TOML datetime.""" 169 | value = parse_rfc3339(raw) 170 | if not isinstance(value, _datetime.datetime): 171 | raise ValueError("datetime() only accepts datetime strings.") 172 | 173 | return item(value) 174 | 175 | 176 | def array(raw: str = "[]") -> Array: 177 | """Create an array item for its string representation. 178 | 179 | :Example: 180 | 181 | >>> array("[1, 2, 3]") # Create from a string 182 | [1, 2, 3] 183 | >>> a = array() 184 | >>> a.extend([1, 2, 3]) # Create from a list 185 | >>> a 186 | [1, 2, 3] 187 | """ 188 | return value(raw) 189 | 190 | 191 | def table(is_super_table: bool | None = None) -> Table: 192 | """Create an empty table. 193 | 194 | :param is_super_table: if true, the table is a super table 195 | 196 | :Example: 197 | 198 | >>> doc = document() 199 | >>> foo = table(True) 200 | >>> bar = table() 201 | >>> bar.update({'x': 1}) 202 | >>> foo.append('bar', bar) 203 | >>> doc.append('foo', foo) 204 | >>> print(doc.as_string()) 205 | [foo.bar] 206 | x = 1 207 | """ 208 | return Table(Container(), Trivia(), False, is_super_table) 209 | 210 | 211 | def inline_table() -> InlineTable: 212 | """Create an inline table. 213 | 214 | :Example: 215 | 216 | >>> table = inline_table() 217 | >>> table.update({'x': 1, 'y': 2}) 218 | >>> print(table.as_string()) 219 | {x = 1, y = 2} 220 | """ 221 | return InlineTable(Container(), Trivia(), new=True) 222 | 223 | 224 | def aot() -> AoT: 225 | """Create an array of table. 226 | 227 | :Example: 228 | 229 | >>> doc = document() 230 | >>> aot = aot() 231 | >>> aot.append(item({'x': 1})) 232 | >>> doc.append('foo', aot) 233 | >>> print(doc.as_string()) 234 | [[foo]] 235 | x = 1 236 | """ 237 | return AoT([]) 238 | 239 | 240 | def key(k: str | Iterable[str]) -> Key: 241 | """Create a key from a string. When a list of string is given, 242 | it will create a dotted key. 243 | 244 | :Example: 245 | 246 | >>> doc = document() 247 | >>> doc.append(key('foo'), 1) 248 | >>> doc.append(key(['bar', 'baz']), 2) 249 | >>> print(doc.as_string()) 250 | foo = 1 251 | bar.baz = 2 252 | """ 253 | if isinstance(k, str): 254 | return SingleKey(k) 255 | return DottedKey([key(_k) for _k in k]) 256 | 257 | 258 | def value(raw: str) -> _Item: 259 | """Parse a simple value from a string. 260 | 261 | :Example: 262 | 263 | >>> value("1") 264 | 1 265 | >>> value("true") 266 | True 267 | >>> value("[1, 2, 3]") 268 | [1, 2, 3] 269 | """ 270 | parser = Parser(raw) 271 | v = parser._parse_value() 272 | if not parser.end(): 273 | raise parser.parse_error(UnexpectedCharError, char=parser._current) 274 | return v 275 | 276 | 277 | def key_value(src: str) -> tuple[Key, _Item]: 278 | """Parse a key-value pair from a string. 279 | 280 | :Example: 281 | 282 | >>> key_value("foo = 1") 283 | (Key('foo'), 1) 284 | """ 285 | return Parser(src)._parse_key_value() 286 | 287 | 288 | def ws(src: str) -> Whitespace: 289 | """Create a whitespace from a string.""" 290 | return Whitespace(src, fixed=True) 291 | 292 | 293 | def nl() -> Whitespace: 294 | """Create a newline item.""" 295 | return ws("\n") 296 | 297 | 298 | def comment(string: str) -> Comment: 299 | """Create a comment item.""" 300 | return Comment(Trivia(comment_ws=" ", comment="# " + string)) 301 | 302 | 303 | def register_encoder(encoder: E) -> E: 304 | """Add a custom encoder, which should be a function that will be called 305 | if the value can't otherwise be converted. 306 | 307 | The encoder should return a TOMLKit item or raise a ``ConvertError``. 308 | 309 | Example: 310 | @register_encoder 311 | def encode_custom_dict(obj, _parent=None, _sort_keys=False): 312 | if isinstance(obj, CustomDict): 313 | tbl = table() 314 | for key, value in obj.items(): 315 | # Pass along parameters when encoding nested values 316 | tbl[key] = item(value, _parent=tbl, _sort_keys=_sort_keys) 317 | return tbl 318 | raise ConvertError("Not a CustomDict") 319 | """ 320 | CUSTOM_ENCODERS.append(encoder) 321 | return encoder 322 | 323 | 324 | def unregister_encoder(encoder: Encoder) -> None: 325 | """Unregister a custom encoder.""" 326 | with contextlib.suppress(ValueError): 327 | CUSTOM_ENCODERS.remove(encoder) 328 | -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | import io 2 | import json 3 | import os 4 | 5 | from datetime import date 6 | from datetime import datetime 7 | from datetime import time 8 | from types import MappingProxyType 9 | 10 | import pytest 11 | 12 | import tomlkit 13 | 14 | from tomlkit import dump 15 | from tomlkit import dumps 16 | from tomlkit import load 17 | from tomlkit import loads 18 | from tomlkit import parse 19 | from tomlkit.exceptions import InvalidCharInStringError 20 | from tomlkit.exceptions import InvalidControlChar 21 | from tomlkit.exceptions import InvalidDateError 22 | from tomlkit.exceptions import InvalidDateTimeError 23 | from tomlkit.exceptions import InvalidNumberError 24 | from tomlkit.exceptions import InvalidStringError 25 | from tomlkit.exceptions import InvalidTimeError 26 | from tomlkit.exceptions import UnexpectedCharError 27 | from tomlkit.items import AoT 28 | from tomlkit.items import Array 29 | from tomlkit.items import Bool 30 | from tomlkit.items import Date 31 | from tomlkit.items import DateTime 32 | from tomlkit.items import Float 33 | from tomlkit.items import InlineTable 34 | from tomlkit.items import Integer 35 | from tomlkit.items import Key 36 | from tomlkit.items import Table 37 | from tomlkit.items import Time 38 | from tomlkit.toml_document import TOMLDocument 39 | 40 | 41 | def json_serial(obj): 42 | """JSON serializer for objects not serializable by default json code""" 43 | if isinstance(obj, (datetime, date, time)): 44 | return obj.isoformat() 45 | 46 | raise TypeError(f"Type {type(obj)} not serializable") 47 | 48 | 49 | @pytest.mark.parametrize( 50 | "example_name", 51 | [ 52 | "example", 53 | "fruit", 54 | "hard", 55 | "sections_with_same_start", 56 | "pyproject", 57 | "0.5.0", 58 | "test", 59 | "newline_in_strings", 60 | "preserve_quotes_in_string", 61 | "string_slash_whitespace_newline", 62 | "table_names", 63 | ], 64 | ) 65 | def test_parse_can_parse_valid_toml_files(example, example_name): 66 | assert isinstance(parse(example(example_name)), TOMLDocument) 67 | assert isinstance(loads(example(example_name)), TOMLDocument) 68 | 69 | 70 | @pytest.mark.parametrize( 71 | "example_name", 72 | [ 73 | "example", 74 | "fruit", 75 | "hard", 76 | "sections_with_same_start", 77 | "pyproject", 78 | "0.5.0", 79 | "test", 80 | "newline_in_strings", 81 | "preserve_quotes_in_string", 82 | "string_slash_whitespace_newline", 83 | "table_names", 84 | ], 85 | ) 86 | def test_load_from_file_object(example_name): 87 | with open( 88 | os.path.join(os.path.dirname(__file__), "examples", example_name + ".toml"), 89 | encoding="utf-8", 90 | ) as fp: 91 | assert isinstance(load(fp), TOMLDocument) 92 | 93 | 94 | @pytest.mark.parametrize("example_name", ["0.5.0", "pyproject", "table_names"]) 95 | def test_parsed_document_are_properly_json_representable( 96 | example, json_example, example_name 97 | ): 98 | doc = json.loads(json.dumps(parse(example(example_name)), default=json_serial)) 99 | json_doc = json.loads(json_example(example_name)) 100 | 101 | assert doc == json_doc 102 | 103 | 104 | @pytest.mark.parametrize( 105 | "example_name,error", 106 | [ 107 | ("section_with_trailing_characters", UnexpectedCharError), 108 | ("key_value_with_trailing_chars", UnexpectedCharError), 109 | ("array_with_invalid_chars", UnexpectedCharError), 110 | ("invalid_number", InvalidNumberError), 111 | ("invalid_date", InvalidDateError), 112 | ("invalid_time", InvalidTimeError), 113 | ("invalid_datetime", InvalidDateTimeError), 114 | ("trailing_comma", UnexpectedCharError), 115 | ("newline_in_singleline_string", InvalidControlChar), 116 | ("string_slash_whitespace_char", InvalidCharInStringError), 117 | ("array_no_comma", UnexpectedCharError), 118 | ("array_duplicate_comma", UnexpectedCharError), 119 | ("array_leading_comma", UnexpectedCharError), 120 | ("inline_table_no_comma", UnexpectedCharError), 121 | ("inline_table_duplicate_comma", UnexpectedCharError), 122 | ("inline_table_leading_comma", UnexpectedCharError), 123 | ("inline_table_trailing_comma", UnexpectedCharError), 124 | ], 125 | ) 126 | def test_parse_raises_errors_for_invalid_toml_files( 127 | invalid_example, error, example_name 128 | ): 129 | with pytest.raises(error): 130 | parse(invalid_example(example_name)) 131 | 132 | 133 | @pytest.mark.parametrize( 134 | "example_name", 135 | [ 136 | "example", 137 | "fruit", 138 | "hard", 139 | "sections_with_same_start", 140 | "pyproject", 141 | "0.5.0", 142 | "test", 143 | "table_names", 144 | ], 145 | ) 146 | def test_original_string_and_dumped_string_are_equal(example, example_name): 147 | content = example(example_name) 148 | parsed = parse(content) 149 | 150 | assert content == dumps(parsed) 151 | 152 | 153 | def test_a_raw_dict_can_be_dumped(): 154 | s = dumps({"foo": "bar"}) 155 | 156 | assert s == 'foo = "bar"\n' 157 | 158 | 159 | def test_mapping_types_can_be_dumped(): 160 | x = MappingProxyType({"foo": "bar"}) 161 | assert dumps(x) == 'foo = "bar"\n' 162 | 163 | 164 | def test_dumps_weird_object(): 165 | with pytest.raises(TypeError): 166 | dumps(object()) 167 | 168 | 169 | def test_dump_tuple_value_as_array(): 170 | x = {"foo": (1, 2)} 171 | assert dumps(x) == "foo = [1, 2]\n" 172 | 173 | x = {"foo": ({"a": 1}, {"a": 2})} 174 | assert dumps(x) == "[[foo]]\na = 1\n\n[[foo]]\na = 2\n" 175 | 176 | 177 | def test_dump_to_file_object(): 178 | doc = {"foo": "bar"} 179 | fp = io.StringIO() 180 | dump(doc, fp) 181 | assert fp.getvalue() == 'foo = "bar"\n' 182 | 183 | 184 | def test_dump_nested_dotted_table(): 185 | a = tomlkit.parse("a.b.c.d='e'")["a"] 186 | assert a == {"b": {"c": {"d": "e"}}} 187 | assert dumps(a) == "b.c.d='e'" 188 | 189 | 190 | def test_integer(): 191 | i = tomlkit.integer("34") 192 | 193 | assert isinstance(i, Integer) 194 | 195 | 196 | def test_float(): 197 | i = tomlkit.float_("34.56") 198 | 199 | assert isinstance(i, Float) 200 | 201 | 202 | def test_boolean(): 203 | i = tomlkit.boolean("true") 204 | 205 | assert isinstance(i, Bool) 206 | 207 | 208 | def test_date(): 209 | dt = tomlkit.date("1979-05-13") 210 | 211 | assert isinstance(dt, Date) 212 | 213 | with pytest.raises(ValueError): 214 | tomlkit.date("12:34:56") 215 | 216 | 217 | def test_time(): 218 | dt = tomlkit.time("12:34:56") 219 | 220 | assert isinstance(dt, Time) 221 | 222 | with pytest.raises(ValueError): 223 | tomlkit.time("1979-05-13") 224 | 225 | 226 | def test_datetime(): 227 | dt = tomlkit.datetime("1979-05-13T12:34:56") 228 | 229 | assert isinstance(dt, DateTime) 230 | 231 | with pytest.raises(ValueError): 232 | tomlkit.time("1979-05-13") 233 | 234 | 235 | def test_array(): 236 | a = tomlkit.array() 237 | 238 | assert isinstance(a, Array) 239 | 240 | a = tomlkit.array("[1,2, 3]") 241 | 242 | assert isinstance(a, Array) 243 | 244 | 245 | def test_table(): 246 | t = tomlkit.table() 247 | 248 | assert isinstance(t, Table) 249 | 250 | 251 | def test_inline_table(): 252 | t = tomlkit.inline_table() 253 | 254 | assert isinstance(t, InlineTable) 255 | 256 | 257 | def test_aot(): 258 | t = tomlkit.aot() 259 | 260 | assert isinstance(t, AoT) 261 | 262 | 263 | def test_key(): 264 | k = tomlkit.key("foo") 265 | 266 | assert isinstance(k, Key) 267 | 268 | 269 | def test_key_value(): 270 | k, i = tomlkit.key_value("foo = 12") 271 | 272 | assert isinstance(k, Key) 273 | assert isinstance(i, Integer) 274 | 275 | 276 | def test_string(): 277 | s = tomlkit.string('foo "') 278 | 279 | assert s.value == 'foo "' 280 | assert s.as_string() == '"foo \\""' 281 | 282 | 283 | def test_item_dict_to_table(): 284 | t = tomlkit.item({"foo": {"bar": "baz"}}) 285 | 286 | assert t.value == {"foo": {"bar": "baz"}} 287 | assert ( 288 | t.as_string() 289 | == """[foo] 290 | bar = "baz" 291 | """ 292 | ) 293 | 294 | 295 | def test_item_mixed_aray(): 296 | example = [{"a": 3}, "b", 42] 297 | expected = '[{a = 3}, "b", 42]' 298 | t = tomlkit.item(example) 299 | assert t.as_string().strip() == expected 300 | assert dumps({"x": {"y": example}}).strip() == "[x]\ny = " + expected 301 | 302 | 303 | def test_build_super_table(): 304 | doc = tomlkit.document() 305 | table = tomlkit.table(True) 306 | table.add("bar", {"x": 1}) 307 | doc.add("foo", table) 308 | assert doc.as_string() == "[foo.bar]\nx = 1\n" 309 | 310 | 311 | def test_add_dotted_key(): 312 | doc = tomlkit.document() 313 | doc.add(tomlkit.key(["foo", "bar"]), 1) 314 | assert doc.as_string() == "foo.bar = 1\n" 315 | 316 | table = tomlkit.table() 317 | table.add(tomlkit.key(["foo", "bar"]), 1) 318 | assert table.as_string() == "foo.bar = 1\n" 319 | 320 | 321 | @pytest.mark.parametrize( 322 | ("raw", "expected"), 323 | [ 324 | ("true", True), 325 | ("false", False), 326 | ], 327 | ) 328 | def test_value_parses_boolean(raw, expected): 329 | parsed = tomlkit.value(raw) 330 | assert parsed == expected 331 | 332 | 333 | @pytest.mark.parametrize( 334 | "raw", ["t", "f", "tru", "fals", "test", "friend", "truthy", "falsify"] 335 | ) 336 | def test_value_rejects_values_looking_like_bool_at_start(raw): 337 | """Reproduces https://github.com/sdispater/tomlkit/issues/165""" 338 | with pytest.raises(tomlkit.exceptions.ParseError): 339 | tomlkit.value(raw) 340 | 341 | 342 | @pytest.mark.parametrize( 343 | "raw", 344 | [ 345 | "truee", 346 | "truely", 347 | "true-thoughts", 348 | "true_hip_hop", 349 | ], 350 | ) 351 | def test_value_rejects_values_having_true_prefix(raw): 352 | """Values that have ``true`` or ``false`` as prefix but then have additional chars are rejected.""" 353 | with pytest.raises(tomlkit.exceptions.ParseError): 354 | tomlkit.value(raw) 355 | 356 | 357 | @pytest.mark.parametrize( 358 | "raw", 359 | [ 360 | "falsee", 361 | "falsely", 362 | "false-ideas", 363 | "false_prophet", 364 | ], 365 | ) 366 | def test_value_rejects_values_having_false_prefix(raw): 367 | """Values that have ``true`` or ``false`` as prefix but then have additional chars are rejected.""" 368 | with pytest.raises(tomlkit.exceptions.ParseError): 369 | tomlkit.value(raw) 370 | 371 | 372 | @pytest.mark.parametrize( 373 | "raw", 374 | [ 375 | '"foo"1.2', 376 | "truefalse", 377 | "1.0false", 378 | "100true", 379 | "truetrue", 380 | "falsefalse", 381 | "1.2.3.4", 382 | "[][]", 383 | "{a=[][]}[]", 384 | "true[]", 385 | "false{a=1}", 386 | ], 387 | ) 388 | def test_value_rejects_values_with_appendage(raw): 389 | """Values that appear valid at the beginning but leave chars unparsed are rejected.""" 390 | with pytest.raises(tomlkit.exceptions.ParseError): 391 | tomlkit.value(raw) 392 | 393 | 394 | def test_create_super_table_with_table(): 395 | data = {"foo": {"bar": {"a": 1}}} 396 | assert dumps(data) == "[foo.bar]\na = 1\n" 397 | 398 | 399 | def test_create_super_table_with_aot(): 400 | data = {"foo": {"bar": [{"a": 1}]}} 401 | assert dumps(data) == "[[foo.bar]]\na = 1\n" 402 | 403 | 404 | @pytest.mark.parametrize( 405 | "kwargs, example, expected", 406 | [ 407 | ({}, "My\nString", '"My\\nString"'), 408 | ({"escape": False}, "My String\t", '"My String\t"'), 409 | ({"literal": True}, "My String\t", "'My String\t'"), 410 | ({"escape": True, "literal": True}, "My String\t", "'My String\t'"), 411 | ({}, "My String\u0001", '"My String\\u0001"'), 412 | ({}, "My String\u000b", '"My String\\u000b"'), 413 | ({}, "My String\x08", '"My String\\b"'), 414 | ({}, "My String\x0c", '"My String\\f"'), 415 | ({}, "My String\x01", '"My String\\u0001"'), 416 | ({}, "My String\x06", '"My String\\u0006"'), 417 | ({}, "My String\x12", '"My String\\u0012"'), 418 | ({}, "My String\x7f", '"My String\\u007f"'), 419 | ({"escape": False}, "My String\u0001", '"My String\u0001"'), 420 | ({"multiline": True}, "\nMy\nString\n", '"""\nMy\nString\n"""'), 421 | ({"multiline": True}, 'My"String', '"""My"String"""'), 422 | ({"multiline": True}, 'My""String', '"""My""String"""'), 423 | ({"multiline": True}, 'My"""String', '"""My""\\"String"""'), 424 | ({"multiline": True}, 'My""""String', '"""My""\\""String"""'), 425 | ( 426 | {"multiline": True}, 427 | '"""My"""Str"""ing"""', 428 | '"""""\\"My""\\"Str""\\"ing""\\""""', 429 | ), 430 | ({"multiline": True, "literal": True}, "My\nString", "'''My\nString'''"), 431 | ({"multiline": True, "literal": True}, "My'String", "'''My'String'''"), 432 | ({"multiline": True, "literal": True}, "My\r\nString", "'''My\r\nString'''"), 433 | ( 434 | {"literal": True}, 435 | r"C:\Users\nodejs\templates", 436 | r"'C:\Users\nodejs\templates'", 437 | ), 438 | ({"literal": True}, r"<\i\c*\s*>", r"'<\i\c*\s*>'"), 439 | ( 440 | {"multiline": True, "literal": True}, 441 | r"I [dw]on't need \d{2} apples", 442 | r"'''I [dw]on't need \d{2} apples'''", 443 | ), 444 | ], 445 | ) 446 | def test_create_string(kwargs, example, expected): 447 | value = tomlkit.string(example, **kwargs) 448 | assert value.as_string() == expected 449 | 450 | 451 | @pytest.mark.parametrize( 452 | "kwargs, example", 453 | [ 454 | ({"literal": True}, "My'String"), 455 | ({"literal": True}, "My\nString"), 456 | ({"literal": True}, "My\r\nString"), 457 | ({"literal": True}, "My\bString"), 458 | ({"literal": True}, "My\x08String"), 459 | ({"literal": True}, "My\x0cString"), 460 | ({"literal": True}, "My\x7fString"), 461 | ({"multiline": True, "literal": True}, "My'''String"), 462 | ], 463 | ) 464 | def test_create_string_with_invalid_characters(kwargs, example): 465 | with pytest.raises(InvalidStringError): 466 | tomlkit.string(example, **kwargs) 467 | 468 | 469 | def test_parse_empty_quoted_table_name(): 470 | content = "['']\nx = 1\n" 471 | parsed = loads(content) 472 | assert parsed == {"": {"x": 1}} 473 | assert dumps(parsed) == content 474 | --------------------------------------------------------------------------------