cli.utils.path: add truncate_path()

This commit is contained in:
bastimeyer 2024-04-03 23:11:23 +02:00 committed by Sebastian Meyer
parent 4abb9ae9cf
commit 54803f5a80
2 changed files with 139 additions and 1 deletions

View File

@ -35,6 +35,28 @@ def replace_chars(path: str, charmap: Optional[str] = None, replacement: str = R
return pattern.sub(replacement, path)
# This method does not take care of unicode modifier characters when truncating
def truncate_path(path: str, length: int = 255, keep_extension: bool = True) -> str:
if len(path) <= length:
return path
parts = path.rsplit(".", 1)
# no file name extension (no dot separator in path or file name extension too long):
# truncate the whole thing
if not keep_extension or len(parts) == 1 or len(parts[1]) > 10:
encoded = path.encode("utf-8")
truncated = encoded[:length]
decoded = truncated.decode("utf-8", errors="ignore")
return decoded
# truncate file name, but keep file name extension
encoded = parts[0].encode("utf-8")
truncated = encoded[:length - len(parts[1]) - 1]
decoded = truncated.decode("utf-8", errors="ignore")
return f"{decoded}.{parts[1]}"
def replace_path(pathlike: Union[str, Path], mapper: Callable[[str], str]) -> Path:
def get_part(part):
newpart = mapper(part)

View File

@ -1,8 +1,10 @@
from pathlib import Path
from string import ascii_lowercase as alphabet
from typing import Tuple
import pytest
from streamlink_cli.utils.path import replace_chars, replace_path
from streamlink_cli.utils.path import replace_chars, replace_path, truncate_path
@pytest.mark.parametrize("char", list(range(32)))
@ -77,3 +79,117 @@ def test_replace_path_expanduser_posix(os_environ):
def test_replace_path_expanduser_windows(os_environ):
assert replace_path("~\\bar", lambda s: s) == Path("C:\\Users\\foo\\bar")
assert replace_path("foo\\bar", lambda s: dict(foo="~").get(s, s)) == Path("~\\bar")
text = alphabet * 10
bear = "🐻" # Unicode character: "Bear Face" (U+1F43B)
bears = bear * 512
@pytest.mark.parametrize(("args", "expected"), [
pytest.param(
(alphabet, 255, True),
alphabet,
id="text - no truncation",
),
pytest.param(
(text, 255, True),
text[:255],
id="text - truncate",
),
pytest.param(
(text, 50, True),
text[:50],
id="text - truncate at 50",
),
pytest.param(
(f"{alphabet}.ext", 255, True),
f"{alphabet}.ext",
id="text+ext1 - no truncation",
),
pytest.param(
(f"{text}.ext", 255, True),
f"{text[:251]}.ext",
id="text+ext1 - truncate",
),
pytest.param(
(f"{text}.ext", 50, True),
f"{text[:46]}.ext",
id="text+ext1 - truncate at 50",
),
pytest.param(
(f"{text}.ext", 255, False),
text[:255],
id="text+ext1+nokeep - truncate",
),
pytest.param(
(f"{text}.ext", 50, False),
text[:50],
id="text+ext1+nokeep - truncate at 50",
),
pytest.param(
(f"{text}.notafilenameextension", 255, True),
text[:255],
id="text+ext2 - truncate",
),
pytest.param(
(f"{text}.notafilenameextension", 50, True),
text[:50],
id="text+ext2 - truncate at 50",
),
pytest.param(
(bear, 255, True),
bear,
id="bear - no truncation",
),
pytest.param(
(bears, 255, True),
bear * 63,
id="bear - truncate",
),
pytest.param(
(bears, 50, True),
bear * 12,
id="bear - truncate at 50",
),
pytest.param(
(f"{bear}.ext", 255, True),
f"{bear}.ext",
id="bear+ext1 - no truncation",
),
pytest.param(
(f"{bears}.ext", 255, True),
f"{bear * 62}.ext",
id="bear+ext1 - truncate",
),
pytest.param(
(f"{bears}.ext", 50, True),
f"{bear * 11}.ext",
id="bear+ext1 - truncate at 50",
),
pytest.param(
(f"{bears}.ext", 255, False),
bear * 63,
id="bear+ext1+nokeep - truncate",
),
pytest.param(
(f"{bears}.ext", 50, False),
bear * 12,
id="bear+ext1+nokeep - truncate at 50",
),
pytest.param(
(f"{bears}.notafilenameextension", 255, True),
bear * 63,
id="bear+ext2 - truncate",
),
pytest.param(
(f"{bears}.notafilenameextension", 50, True),
bear * 12,
id="bear+ext2 - truncate at 50",
),
])
def test_truncate_path(args: Tuple[str, int, bool], expected: str):
path, length, keep_extension = args
result = truncate_path(path, length, keep_extension)
assert len(result.encode("utf-8")) <= length
assert result == expected