diff --git a/tests/test_package.py b/tests/test_package.py index d843fa17..aaed41b2 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -463,3 +463,56 @@ def test_setuptools_license_file(read_data, filtered, monkeypatch): package = package_file.PackageFile.from_filename(filename, comment=None) meta = package.metadata_dictionary() assert filtered != ("license_file" in meta) + + +@pytest.mark.parametrize("description,expected", + [ + pytest.param( + "\n" + "Two\n" + "Lines\n", + "Two\nLines\n", + id="body", + ), + pytest.param( + "Description: Two\n" + " |Lines\n" + " |\n", + "Two\nLines\n", + id="multiline-header", + ), + pytest.param( + "Description: Two\n" + " Lines\n" + " \n", + "Two\nLines\n", + id="multiline-header-setuptools", + ), + pytest.param( + "Description: Two\n" + " Lines\n" + " Maybe Three", + "Two\n Lines\n Maybe Three", + id="multiline-inconsistent", + ), + pytest.param( + "Description: Two\n" + " |Lines\n" + " Maybe Three", + "Two\n |Lines\n Maybe Three", + id="multiline-mixed", + ), + ], +) +def test_description_field_continuation(description, expected, monkeypatch): + """License-File metadata entries are kept when Metadata-Version is 2.4.""" + read_data = ( + "Metadata-Version: 2.4\n" + "Name: test-package\n" + "Version: 1.0.0\n" + + description + ) + monkeypatch.setattr(package_file.wheel.Wheel, "read", lambda _: read_data) + filename = "tests/fixtures/twine-1.5.0-py2.py3-none-any.whl" + package = package_file.PackageFile.from_filename(filename, comment=None) + assert package.metadata["description"] == expected diff --git a/twine/package.py b/twine/package.py index 831bcdbd..0cf23fde 100644 --- a/twine/package.py +++ b/twine/package.py @@ -65,6 +65,28 @@ def _safe_name(name: str) -> str: return re.sub("[^A-Za-z0-9.]+", "-", name) +def _dedent(string: str) -> str: + """Remove line continuation suffix used for the ``Description`` metadata field. + + The metadata standard prescribes that continuation lines should be + prefixed with 7 spaces followed by a pipe character, however, setuptools + used to prefix continuation lines with 8 spaces. Handle both here. + + If not all lines following the first start with either of the accepted + prefixes, the string is returned without modifications. + """ + lines = string.splitlines() + if ( + False # This is here only to force black into something sensible. + or all(line.startswith(" |") for line in lines[1:]) + or all(line.startswith(" ") for line in lines[1:]) + ): + for i in range(1, len(lines)): + lines[i] = lines[i][8:] + return "\n".join(lines) + return string + + # Map ``metadata.RawMetadata`` fields to ``PackageMetadata`` fields. Some # fields are renamed to match the names expected in the upload form. _RAW_TO_PACKAGE_METADATA = { @@ -232,6 +254,11 @@ def from_filename(cls, filename: str, comment: Optional[str]) -> "PackageFile": # than 2.4. if version.Version(meta.get("metadata_version", "0")) < version.Version("2.4"): meta.pop("license_files", None) + + description = meta.get("description", None) + if description is not None: + meta["description"] = _dedent(description) + try: metadata.Metadata.from_raw(meta) except metadata.ExceptionGroup as group: