Skip to content

Commit 40fbc8e

Browse files
committed
Fix and simplify path truncation
1 parent b3fd84b commit 40fbc8e

File tree

2 files changed

+29
-28
lines changed

2 files changed

+29
-28
lines changed

beets/util/__init__.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -696,22 +696,26 @@ def sanitize_path(path: str, replacements: Replacements | None = None) -> str:
696696
return os.path.join(*comps)
697697

698698

699-
def truncate_path(path: AnyStr) -> AnyStr:
700-
"""Given a bytestring path or a Unicode path fragment, truncate the
701-
components to a legal length. In the last component, the extension
702-
is preserved.
699+
def truncate_str(s: str, length: int) -> str:
700+
"""Truncate the string to the given byte length.
701+
702+
If we end up truncating a unicode character in the middle (rendering it invalid),
703+
it is removed:
704+
705+
>>> s = "🎹🎶" # 8 bytes
706+
>>> truncate_str(s, 6)
707+
'🎹'
703708
"""
704-
max_length = get_max_filename_length()
705-
comps = components(path)
709+
return os.fsencode(s)[:length].decode(sys.getfilesystemencoding(), "ignore")
706710

707-
out = [c[:length] for c in comps]
708-
base, ext = os.path.splitext(comps[-1])
709-
if ext:
710-
# Last component has an extension.
711-
base = base[: max_length - len(ext)]
712-
out[-1] = base + ext
713711

714-
return os.path.join(*out)
712+
def truncate_path(str_path: str) -> str:
713+
"""Truncate each path part to a legal length preserving the extension."""
714+
max_length = get_max_filename_length()
715+
path = Path(str_path)
716+
parent_parts = [truncate_str(p, max_length) for p in path.parts[:-1]]
717+
stem = truncate_str(path.stem, max_length - len(path.suffix))
718+
return str(Path(*parent_parts, stem).with_suffix(path.suffix))
715719

716720

717721
def _legalize_stage(

test/test_util.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -175,18 +175,15 @@ def test_bytesting_path_windows_removes_magic_prefix(self):
175175
assert outpath == "C:\\caf\xe9".encode()
176176

177177

178-
class PathTruncationTest(BeetsTestCase):
179-
def test_truncate_bytestring(self):
180-
with _common.platform_posix():
181-
p = util.truncate_path(b"abcde/fgh", 4)
182-
assert p == b"abcd/fgh"
183-
184-
def test_truncate_unicode(self):
185-
with _common.platform_posix():
186-
p = util.truncate_path("abcde/fgh", 4)
187-
assert p == "abcd/fgh"
188-
189-
def test_truncate_preserves_extension(self):
190-
with _common.platform_posix():
191-
p = util.truncate_path("abcde/fgh.ext", 5)
192-
assert p == "abcde/f.ext"
178+
@patch("beets.util.get_max_filename_length", lambda: 5)
179+
@pytest.mark.parametrize(
180+
"path, expected",
181+
[
182+
("abcdeX/fgh", "abcde/fgh"),
183+
("abcde/fXX.ext", "abcde/f.ext"),
184+
("a🎹/a.ext", "a🎹/a.ext"),
185+
("ab🎹/a.ext", "ab/a.ext"),
186+
],
187+
)
188+
def test_truncate_path(path, expected):
189+
assert util.truncate_path(path) == expected

0 commit comments

Comments
 (0)