Fix and simplify path truncation

This commit is contained in:
Šarūnas Nejus 2025-03-10 03:16:37 +00:00
parent b3fd84b356
commit 40fbc8ee7e
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
2 changed files with 30 additions and 29 deletions

View file

@ -696,22 +696,26 @@ def sanitize_path(path: str, replacements: Replacements | None = None) -> str:
return os.path.join(*comps)
def truncate_path(path: AnyStr) -> AnyStr:
"""Given a bytestring path or a Unicode path fragment, truncate the
components to a legal length. In the last component, the extension
is preserved.
def truncate_str(s: str, length: int) -> str:
"""Truncate the string to the given byte length.
If we end up truncating a unicode character in the middle (rendering it invalid),
it is removed:
>>> s = "🎹🎶" # 8 bytes
>>> truncate_str(s, 6)
'🎹'
"""
return os.fsencode(s)[:length].decode(sys.getfilesystemencoding(), "ignore")
def truncate_path(str_path: str) -> str:
"""Truncate each path part to a legal length preserving the extension."""
max_length = get_max_filename_length()
comps = components(path)
out = [c[:length] for c in comps]
base, ext = os.path.splitext(comps[-1])
if ext:
# Last component has an extension.
base = base[: max_length - len(ext)]
out[-1] = base + ext
return os.path.join(*out)
path = Path(str_path)
parent_parts = [truncate_str(p, max_length) for p in path.parts[:-1]]
stem = truncate_str(path.stem, max_length - len(path.suffix))
return str(Path(*parent_parts, stem).with_suffix(path.suffix))
def _legalize_stage(

View file

@ -175,18 +175,15 @@ class PathConversionTest(BeetsTestCase):
assert outpath == "C:\\caf\xe9".encode()
class PathTruncationTest(BeetsTestCase):
def test_truncate_bytestring(self):
with _common.platform_posix():
p = util.truncate_path(b"abcde/fgh", 4)
assert p == b"abcd/fgh"
def test_truncate_unicode(self):
with _common.platform_posix():
p = util.truncate_path("abcde/fgh", 4)
assert p == "abcd/fgh"
def test_truncate_preserves_extension(self):
with _common.platform_posix():
p = util.truncate_path("abcde/fgh.ext", 5)
assert p == "abcde/f.ext"
@patch("beets.util.get_max_filename_length", lambda: 5)
@pytest.mark.parametrize(
"path, expected",
[
("abcdeX/fgh", "abcde/fgh"),
("abcde/fXX.ext", "abcde/f.ext"),
("a🎹/a.ext", "a🎹/a.ext"),
("ab🎹/a.ext", "ab/a.ext"),
],
)
def test_truncate_path(path, expected):
assert util.truncate_path(path) == expected