diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index cd62b4a4..e8a4b22b 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -5,6 +5,8 @@ from typing import Dict from typing import List from typing import final +from yt_dlp.utils import sanitize_filename + from ytdl_sub.utils.exceptions import InvalidVariableNameException from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException @@ -151,24 +153,23 @@ class StringFormatterValidator(Validator): value=formatter.format_string.format(**OrderedDict(variable_dict)), ) - def apply_formatter(self, variable_dict: Dict[str, str]) -> str: - """ - Calls `format` on the format string using the variable_dict as input kwargs - - Parameters - ---------- - variable_dict - kwargs to pass to the format string - - Returns - ------- - Format string formatted - """ - # Keep formatting the format string until no format_variables are present + def _apply_formatter(self, variable_dict: Dict[str, str], resolve_sanitized: bool = False): formatter = self recursion_depth = 0 max_depth = self._max_format_recursion + if resolve_sanitized: + for format_variable in formatter.format_variables: + # Must resolve the sanitized variable completely + if format_variable.endswith("_sanitized"): + # pylint: disable=protected-access + variable_dict[format_variable] = sanitize_filename( + StringFormatterValidator( + name=self._name, value=f"{{{format_variable}}}" + )._apply_formatter(variable_dict, resolve_sanitized=False) + ) + # pylint: enable=protected-access + while formatter.format_variables and recursion_depth < max_depth: formatter = self.__apply_formatter(formatter=formatter, variable_dict=variable_dict) recursion_depth += 1 @@ -183,6 +184,21 @@ class StringFormatterValidator(Validator): return formatter.format_string + def apply_formatter(self, variable_dict: Dict[str, str]) -> str: + """ + Calls `format` on the format string using the variable_dict as input kwargs + + Parameters + ---------- + variable_dict + kwargs to pass to the format string + + Returns + ------- + Format string formatted + """ + return self._apply_formatter(variable_dict=variable_dict, resolve_sanitized=True) + # pylint: disable=line-too-long class OverridesStringFormatterValidator(StringFormatterValidator): diff --git a/tests/unit/validators/test_string_formatter_validator.py b/tests/unit/validators/test_string_formatter_validator.py index 4a30ba7b..9481ec2a 100644 --- a/tests/unit/validators/test_string_formatter_validator.py +++ b/tests/unit/validators/test_string_formatter_validator.py @@ -1,4 +1,5 @@ import pytest +from yt_dlp.utils import sanitize_filename from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.exceptions import ValidationException @@ -146,6 +147,18 @@ class TestStringFormatterValidator(object): assert format_string.apply_formatter(variable_dict=variable_dict) == expected_string + def test_entry_formatter_override_sanitized_recursive(self, string_formatter_class): + variable_dict = { + "level_a": "level a", + "level_b": "level b ? {level_a}", + "level_c_sanitized": "level c and {level_b}", + } + + format_string = string_formatter_class(name="test", value="level d and {level_c_sanitized}") + expected_string = "level d and " + sanitize_filename("level c and level b ? level a") + + assert format_string.apply_formatter(variable_dict=variable_dict) == expected_string + def test_entry_formatter_override_recursive_fail_cycle(self, string_formatter_class): variable_dict = { "level_a": "{level_b}",