cli.utils.formatter: respect max file name length

This commit is contained in:
bastimeyer 2024-04-04 00:04:14 +02:00 committed by Sebastian Meyer
parent 54803f5a80
commit eecf60935a
4 changed files with 34 additions and 13 deletions

View File

@ -2,17 +2,18 @@ from pathlib import Path
from typing import Optional
from streamlink.utils.formatter import Formatter as _BaseFormatter
from streamlink_cli.utils.path import replace_chars, replace_path
from streamlink_cli.utils.path import replace_chars, replace_path, truncate_path
class Formatter(_BaseFormatter):
title = _BaseFormatter.format
def path(self, pathlike: str, charmap: Optional[str] = None) -> Path:
def path(self, pathlike: str, charmap: Optional[str] = None, max_filename_length: int = 255) -> Path:
def mapper(s):
return replace_chars(s, charmap)
def format_part(part):
return self._format(part, mapper, {})
def format_part(part: str, isfile: bool) -> str:
formatted = self._format(part, mapper, {})
return truncate_path(formatted, max_filename_length, keep_extension=isfile)
return replace_path(pathlike, format_part)

View File

@ -57,9 +57,12 @@ def truncate_path(path: str, length: int = 255, keep_extension: bool = True) ->
return f"{decoded}.{parts[1]}"
def replace_path(pathlike: Union[str, Path], mapper: Callable[[str], str]) -> Path:
def get_part(part):
newpart = mapper(part)
def replace_path(pathlike: Union[str, Path], mapper: Callable[[str, bool], str]) -> Path:
def get_part(part: str, isfile: bool) -> str:
newpart = mapper(part, isfile)
return REPLACEMENT if part != newpart and newpart in SPECIAL_PATH_PARTS else newpart
return Path(*(get_part(part) for part in Path(pathlike).expanduser().parts))
parts = Path(pathlike).expanduser().parts
last = len(parts) - 1
return Path(*(get_part(part, i == last) for i, part in enumerate(parts)))

View File

@ -1,6 +1,7 @@
from datetime import datetime
from os.path import sep
from pathlib import Path
from string import ascii_letters as alphabet
from unittest.mock import Mock, call, patch
import pytest
@ -79,3 +80,19 @@ class TestCLIFormatter:
path = formatter.path(f"{{current}}{sep}{{parent}}{sep}{{dots}}{sep}{{separator}}{sep}foo{sep}.{sep}..{sep}bar")
assert path == Path("_", "_", "...", "_", "foo", ".", "..", "bar"), \
"Formats the path's parts separately and ignores current and parent directories in substitutions only"
def test_path_truncation_ascii(self, formatter: Formatter):
formatter.mapping.update({
"dir": lambda: alphabet * 10,
"file": lambda: alphabet * 10,
})
path = formatter.path(f"{{dir}}.fakeext{sep}{{file}}.ext")
assert path == Path((alphabet * 10)[:255], f"{(alphabet * 10)[:251]}.ext")
def test_path_truncation_unicode(self, formatter: Formatter):
formatter.mapping.update({
"dir": lambda: "🐻" * 512,
"file": lambda: "🐻" * 512,
})
path = formatter.path(f"{{dir}}.fakeext{sep}{{file}}.ext")
assert path == Path("🐻" * 63, f"{'🐻' * 62}.ext")

View File

@ -59,7 +59,7 @@ def test_replace_chars_replacement():
def test_replace_path():
def mapper(s):
def mapper(s, *_):
return dict(foo=".", bar="..").get(s, s)
path = Path("foo", ".", "bar", "..", "baz")
@ -70,15 +70,15 @@ def test_replace_path():
@pytest.mark.posix_only()
@pytest.mark.parametrize("os_environ", [pytest.param({"HOME": "/home/foo"}, id="posix")], indirect=True)
def test_replace_path_expanduser_posix(os_environ):
assert replace_path("~/bar", lambda s: s) == Path("/home/foo/bar")
assert replace_path("foo/bar", lambda s: dict(foo="~").get(s, s)) == Path("~/bar")
assert replace_path("~/bar", lambda s, *_: s) == Path("/home/foo/bar")
assert replace_path("foo/bar", lambda s, *_: dict(foo="~").get(s, s)) == Path("~/bar")
@pytest.mark.windows_only()
@pytest.mark.parametrize("os_environ", [pytest.param({"USERPROFILE": "C:\\Users\\foo"}, id="windows")], indirect=True)
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")
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