diff --git a/setup.cfg b/setup.cfg index 98d4b1f..127c3ad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,14 +62,14 @@ exclude = full = importlib-metadata; python_version<"3.8" configupdater>=3.0.1,<4 - tomlkit>=0.9.2,<2 + tomlkit>=0.10.0,<2 # atoml @ git+https://github.com/abravalheri/atoml@table-common-ancestor#egg=atoml lite = importlib-metadata; python_version<"3.8" tomli-w>=0.4.0,<2 all = configupdater>=3.0.1,<4 - tomlkit>=0.9.2,<2 + tomlkit>=0.10.0,<2 # atoml @ git+https://github.com/abravalheri/atoml@table-common-ancestor#egg=atoml tomli-w>=0.4.0,<2 diff --git a/src/ini2toml/drivers/full_toml.py b/src/ini2toml/drivers/full_toml.py index 84ea36e..38ae1f7 100644 --- a/src/ini2toml/drivers/full_toml.py +++ b/src/ini2toml/drivers/full_toml.py @@ -18,9 +18,10 @@ item, loads, nl, + string, table, ) -from tomlkit.items import AoT, Array, InlineTable, Item, Table +from tomlkit.items import AoT, Array, InlineTable, Item, String, Table from tomlkit.toml_document import TOMLDocument from ..errors import InvalidTOMLKey @@ -59,6 +60,11 @@ def collapse(obj, root=False): return obj +@collapse.register(str) +def _collapse_string(obj: str) -> String: + return _string(obj) + + @collapse.register(Commented) def _collapse_commented(obj: Commented, root=False) -> Item: return create_item(obj.value_or(None), obj.comment) @@ -235,7 +241,7 @@ def _convert_irepr_to_toml(irepr: IntermediateRepr, out: T) -> T: def create_item(value, comment): - obj = item(value) + obj = _item(value) if comment is not None: obj.comment(comment) return obj @@ -291,3 +297,28 @@ def classify_list(seq: Sequence) -> Tuple[bool, int, int, bool, bool, int]: has_nl = has_nl or "\n" in elem_repr return is_aot, max_len, total_len, has_nl, has_nested, len(seq) + + +def _item(obj) -> Item: + if isinstance(obj, str): + return _string(obj) + + return item(obj) + + +def _string(obj: str) -> String: + """Try to guess the best TOML representation for a string""" + multiline = "\n" in obj + single_line = "".join(x.strip().rstrip("\\") for x in obj.splitlines()) + literal = '"' in obj or "\\" in single_line + + if multiline and not obj.startswith("\n"): + # TOML will automatically strip an starting newline + # so let's add it, since it is better for reading + obj = "\n" + obj + + try: + return string(obj, literal=literal, multiline=multiline) + except ValueError: + # Literal strings are not always possible + return string(obj, multiline=multiline) diff --git a/tests/examples/django/pyproject.toml b/tests/examples/django/pyproject.toml index f47aed6..a729c5e 100644 --- a/tests/examples/django/pyproject.toml +++ b/tests/examples/django/pyproject.toml @@ -29,7 +29,7 @@ dynamic = ["license", "version"] requires-python = ">=3.8" dependencies = [ "asgiref >= 3.3.2", - "backports.zoneinfo; python_version<\"3.9\"", + 'backports.zoneinfo; python_version<"3.9"', "sqlparse >= 0.2.2", "tzdata; sys_platform == 'win32'", ] diff --git a/tests/examples/flask/pyproject.toml b/tests/examples/flask/pyproject.toml index 53bff4b..c6e9c0d 100644 --- a/tests/examples/flask/pyproject.toml +++ b/tests/examples/flask/pyproject.toml @@ -79,10 +79,22 @@ source = [ # B9 = bugbear opinions # ISC = implicit-str-concat select = "B, E, F, W, B9, ISC" -ignore = "\n# slice notation whitespace, invalid\nE203\n# import at top, too many circular import fixes\nE402\n# line length, handled by bugbear B950\nE501\n# bare except, handled by bugbear B001\nE722\n# bin op line break, invalid\nW503" +ignore = """ +# slice notation whitespace, invalid +E203 +# import at top, too many circular import fixes +E402 +# line length, handled by bugbear B950 +E501 +# bare except, handled by bugbear B001 +E722 +# bin op line break, invalid +W503""" # up to 88 allowed by bugbear B950 max-line-length = "80" -per-file-ignores = "\n# __init__ module exports names\nsrc/flask/__init__.py: F401" +per-file-ignores = """ +# __init__ module exports names +src/flask/__init__.py: F401""" [tool.mypy] files = ["src/flask"] diff --git a/tests/examples/pandas/pyproject.toml b/tests/examples/pandas/pyproject.toml index d8ce6d1..0f7f18a 100644 --- a/tests/examples/pandas/pyproject.toml +++ b/tests/examples/pandas/pyproject.toml @@ -80,19 +80,90 @@ parentdir_prefix = "pandas-" [tool.flake8] max-line-length = "88" -ignore = "\n# space before : (needed for how black formats slicing)\nE203,\n# line break before binary operator\nW503,\n# line break after binary operator\nW504,\n# module level import not at top of file\nE402,\n# do not assign a lambda expression, use a def\nE731,\n# found modulo formatter (incorrect picks up mod operations)\nS001,\n# controversial\nB005,\n# controversial\nB006,\n# controversial\nB007,\n# controversial\nB008,\n# setattr is used to side-step mypy\nB009,\n# getattr is used to side-step mypy\nB010,\n# tests use assert False\nB011,\n# tests use comparisons but not their returned value\nB015,\n# false positives\nB301" -exclude = "\ndoc/sphinxext/*.py,\ndoc/build/*.py,\ndoc/temp/*.py,\n.eggs/*.py,\nversioneer.py,\n# exclude asv benchmark environments from linting\nenv" -per-file-ignores = "\n# private import across modules\npandas/tests/*:PDF020\n# pytest.raises without match=\npandas/tests/extension/*:PDF009\n# os.remove\ndoc/make.py:PDF008\n# import from pandas._testing\npandas/testing.py:PDF014" +ignore = """ +# space before : (needed for how black formats slicing) +E203, +# line break before binary operator +W503, +# line break after binary operator +W504, +# module level import not at top of file +E402, +# do not assign a lambda expression, use a def +E731, +# found modulo formatter (incorrect picks up mod operations) +S001, +# controversial +B005, +# controversial +B006, +# controversial +B007, +# controversial +B008, +# setattr is used to side-step mypy +B009, +# getattr is used to side-step mypy +B010, +# tests use assert False +B011, +# tests use comparisons but not their returned value +B015, +# false positives +B301""" +exclude = """ +doc/sphinxext/*.py, +doc/build/*.py, +doc/temp/*.py, +.eggs/*.py, +versioneer.py, +# exclude asv benchmark environments from linting +env""" +per-file-ignores = """ +# private import across modules +pandas/tests/*:PDF020 +# pytest.raises without match= +pandas/tests/extension/*:PDF009 +# os.remove +doc/make.py:PDF008 +# import from pandas._testing +pandas/testing.py:PDF014""" [tool.flake8-rst] max-line-length = "84" -bootstrap = "\nimport numpy as np\nimport pandas as pd\n# avoiding error when importing again numpy or pandas\nnp\n# (in some cases we want to do it to show users)\npd" -ignore = "\n# space before : (needed for how black formats slicing)\nE203,\n# module level import not at top of file\nE402,\n# line break before binary operator\nW503,\n# Classes/functions in different blocks can generate those errors\n# expected 2 blank lines, found 0\nE302,\n# expected 2 blank lines after class or function definition, found 0\nE305,\n# We use semicolon at the end to avoid displaying plot objects\n# statement ends with a semicolon\nE703,\n# comparison to none should be 'if cond is none:'\nE711," -exclude = "\ndoc/source/development/contributing_docstring.rst,\n# work around issue of undefined variable warnings\n# https://github.com/pandas-dev/pandas/pull/38837#issuecomment-752884156\ndoc/source/getting_started/comparison/includes/*.rst" +bootstrap = """ +import numpy as np +import pandas as pd +# avoiding error when importing again numpy or pandas +np +# (in some cases we want to do it to show users) +pd""" +ignore = """ +# space before : (needed for how black formats slicing) +E203, +# module level import not at top of file +E402, +# line break before binary operator +W503, +# Classes/functions in different blocks can generate those errors +# expected 2 blank lines, found 0 +E302, +# expected 2 blank lines after class or function definition, found 0 +E305, +# We use semicolon at the end to avoid displaying plot objects +# statement ends with a semicolon +E703, +# comparison to none should be 'if cond is none:' +E711,""" +exclude = """ +doc/source/development/contributing_docstring.rst, +# work around issue of undefined variable warnings +# https://github.com/pandas-dev/pandas/pull/38837#issuecomment-752884156 +doc/source/getting_started/comparison/includes/*.rst""" [tool.codespell] ignore-words-list = "ba,blocs,coo,hist,nd,sav,ser" -ignore-regex = "https://(\\w+\\.)+" +ignore-regex = 'https://(\w+\.)+' [tool.coverage.run] branch = true @@ -114,7 +185,7 @@ exclude_lines = [ "pragma: no cover", # Don't complain about missing debug-only code: "def __repr__", - "if self\\.debug", + 'if self\.debug', # Don't complain if tests don't hit defensive assertion code: "raise AssertionError", "raise NotImplementedError", diff --git a/tests/examples/pluggy/pyproject.toml b/tests/examples/pluggy/pyproject.toml index 7df5ff4..8b67133 100644 --- a/tests/examples/pluggy/pyproject.toml +++ b/tests/examples/pluggy/pyproject.toml @@ -32,7 +32,7 @@ classifiers = [ urls = {Homepage = "https://github.com/pytest-dev/pluggy"} dynamic = ["license", "version"] requires-python = ">=3.6" -dependencies = ["importlib-metadata>=0.12;python_version<\"3.8\""] +dependencies = ['importlib-metadata>=0.12;python_version<"3.8"'] [project.readme] file = "README.rst" diff --git a/tests/examples/pyscaffold/pyproject.toml b/tests/examples/pyscaffold/pyproject.toml index c132809..2c1770e 100644 --- a/tests/examples/pyscaffold/pyproject.toml +++ b/tests/examples/pyscaffold/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ dynamic = ["license", "version"] requires-python = ">=3.6" dependencies = [ - "importlib-metadata; python_version<\"3.8\"", + 'importlib-metadata; python_version<"3.8"', "appdirs>=1.4.4,<2", "configupdater>=3.0,<4", "setuptools>=46.1.0", @@ -125,7 +125,9 @@ license-files = ["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"] # in order to write a coverage file that can be read by Jenkins. # CAUTION: --cov flags may prohibit setting breakpoints while debugging. # Comment those flags to avoid this pytest issue. -addopts = "--cov pyscaffold --cov-config .coveragerc --cov-report term-missing\n--verbose" +addopts = """ +--cov pyscaffold --cov-config .coveragerc --cov-report term-missing +--verbose""" # In order to use xdist, the developer can add, for example, the following # arguments: # --dist=load --numprocesses=auto @@ -160,7 +162,13 @@ formats = "bdist_wheel" max_line_length = "88" # E203 and W503 have edge cases handled by black extend_ignore = "E203, W503" -exclude = "\nsrc/pyscaffold/contrib\n.tox\nbuild\ndist\n.eggs\ndocs/conf.py" +exclude = """ +src/pyscaffold/contrib +.tox +build +dist +.eggs +docs/conf.py""" [tool.mypy] ignore_missing_imports = true diff --git a/tests/examples/setuptools_docs/pyproject.toml b/tests/examples/setuptools_docs/pyproject.toml index 8cc0aa4..4bef58c 100644 --- a/tests/examples/setuptools_docs/pyproject.toml +++ b/tests/examples/setuptools_docs/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ dynamic = ["readme", "license", "version"] dependencies = [ "requests", - "importlib; python_version == \"2.6\"", + 'importlib; python_version == "2.6"', ] [project.optional-dependencies] diff --git a/tests/examples/virtualenv/pyproject.toml b/tests/examples/virtualenv/pyproject.toml index e3dff3a..6587524 100644 --- a/tests/examples/virtualenv/pyproject.toml +++ b/tests/examples/virtualenv/pyproject.toml @@ -38,8 +38,8 @@ dependencies = [ "filelock>=3.0.0,<4", "platformdirs>=2,<3", "six>=1.9.0,<2", # keep it >=1.9.0 as it may cause problems on LTS platforms - "importlib-metadata>=0.12;python_version<\"3.8\"", - "importlib-resources>=1.0;python_version<\"3.7\"", + 'importlib-metadata>=0.12;python_version<"3.8"', + 'importlib-resources>=1.0;python_version<"3.7"', "pathlib2>=2.3.3,<3;python_version < '3.4' and sys.platform != 'win32'", ] @@ -99,7 +99,7 @@ testing = [ "pytest-mock>=2", "pytest-randomly>=1", "pytest-timeout>=1", - "packaging>=20.0;python_version>\"3.4\"", + 'packaging>=20.0;python_version>"3.4"', ] [project.scripts] @@ -138,4 +138,6 @@ universal = true markers = ["slow"] junit_family = "xunit2" addopts = "--tb=auto -ra --showlocals --no-success-flaky-report" -env = "PYTHONWARNINGS=ignore:DEPRECATION::pip._internal.cli.base_command\nPYTHONIOENCODING=utf-8" +env = """ +PYTHONWARNINGS=ignore:DEPRECATION::pip._internal.cli.base_command +PYTHONIOENCODING=utf-8""" diff --git a/tests/examples/zipp/pyproject.toml b/tests/examples/zipp/pyproject.toml index 0c0dca1..6a5d09a 100644 --- a/tests/examples/zipp/pyproject.toml +++ b/tests/examples/zipp/pyproject.toml @@ -25,9 +25,9 @@ testing = [ "pytest >= 6", "pytest-checkdocs >= 2.4", "pytest-flake8", - "pytest-black >= 0.3.7; python_implementation != \"PyPy\"", # workaround for jaraco/skeleton#22 + 'pytest-black >= 0.3.7; python_implementation != "PyPy"', # workaround for jaraco/skeleton#22 "pytest-cov", - "pytest-mypy; python_implementation != \"PyPy\"", # workaround for jaraco/skeleton#22 + 'pytest-mypy; python_implementation != "PyPy"', # workaround for jaraco/skeleton#22 "pytest-enabler >= 1.0.1", # local "jaraco.itertools", diff --git a/tests/plugins/test_setuptools_pep621.py b/tests/plugins/test_setuptools_pep621.py index 57bb3ce..7aff180 100644 --- a/tests/plugins/test_setuptools_pep621.py +++ b/tests/plugins/test_setuptools_pep621.py @@ -130,7 +130,7 @@ def test_convert_directives(plugin, parse, convert): zip-safe = false # comment package-dir = {"" = "src"} install-requires = [ - "importlib-metadata; python_version<\\"3.8\\"", + 'importlib-metadata; python_version<"3.8"', "configupdater>=3,<=4", ]