From 10cb82a69aa3b37845435b046f52fbaec805701a Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 20 Sep 2024 09:23:22 -0700 Subject: [PATCH] [BACKEND] `%contains_any` function, list + dict support for tilda overrides mode (#1054) Adds the function `%contains_any`, and list support in a tilda override subscription. The end-goal of these features are to more easily add a title exclude list, like so: ``` __preset__: filter_exclude: - "{%contains_any( %lower(title), exclude_title_strings )}" ... Jellyfin TV Show by Date: ~History Documentaries: url: "https://..." exclude_title_strings: - "trailer" - "preview" ``` A proper prebuilt preset or built-in functionality will follow this change. --- .../scripting/scripting_functions.rst | 7 +++++++ src/ytdl_sub/script/functions/string_functions.py | 15 +++++++++++++++ .../subscriptions/subscription_validators.py | 6 ++++-- tests/unit/config/test_subscription.py | 8 ++++++++ .../script/functions/test_string_functions.py | 8 ++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/source/config_reference/scripting/scripting_functions.rst b/docs/source/config_reference/scripting/scripting_functions.rst index ebb777d4..d04a05e4 100644 --- a/docs/source/config_reference/scripting/scripting_functions.rst +++ b/docs/source/config_reference/scripting/scripting_functions.rst @@ -595,6 +595,13 @@ contains :description: Returns True if ``contains`` is in ``string``. False otherwise. +contains_any +~~~~~~~~~~~~ +:spec: ``contains_any(string: String, contains_array: Array) -> Boolean`` + +:description: + Returns true if any element in ``contains_array`` is in ``string``. False otherwise. + lower ~~~~~ :spec: ``lower(string: String) -> String`` diff --git a/src/ytdl_sub/script/functions/string_functions.py b/src/ytdl_sub/script/functions/string_functions.py index fd0cac9f..0819a6f5 100644 --- a/src/ytdl_sub/script/functions/string_functions.py +++ b/src/ytdl_sub/script/functions/string_functions.py @@ -3,6 +3,7 @@ from typing import Optional from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.resolvable import AnyArgument from ytdl_sub.script.types.resolvable import Boolean +from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.types.resolvable import Numeric from ytdl_sub.script.types.resolvable import String @@ -25,6 +26,20 @@ class StringFunctions: """ return Boolean(contains.value in string.value) + @staticmethod + def contains_any(string: String, contains_array: Array) -> Boolean: + """ + :description: + Returns true if any element in ``contains_array`` is in ``string``. False otherwise. + """ + return Boolean( + any( + str(val) in string.value + for val in contains_array.value + if isinstance(val, (String, Integer, Boolean, Float)) + ) + ) + @staticmethod def slice(string: String, start: Integer, end: Optional[Integer] = None) -> String: """ diff --git a/src/ytdl_sub/subscriptions/subscription_validators.py b/src/ytdl_sub/subscriptions/subscription_validators.py index 35bbbcb7..9287a15b 100644 --- a/src/ytdl_sub/subscriptions/subscription_validators.py +++ b/src/ytdl_sub/subscriptions/subscription_validators.py @@ -11,7 +11,7 @@ from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.overrides import Overrides from ytdl_sub.entries.variables.override_variables import SubscriptionVariables from ytdl_sub.utils.script import ScriptUtils -from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator +from ytdl_sub.validators.string_formatter_validators import UnstructuredDictFormatterValidator from ytdl_sub.validators.validators import DictValidator from ytdl_sub.validators.validators import LiteralDictValidator from ytdl_sub.validators.validators import StringListValidator @@ -196,7 +196,9 @@ class SubscriptionListValuesValidator(SubscriptionLeafValidator, StringListValid ) -class SubscriptionWithOverridesValidator(SubscriptionLeafValidator, DictFormatterValidator): +class SubscriptionWithOverridesValidator( + SubscriptionLeafValidator, UnstructuredDictFormatterValidator +): def __init__( self, name, diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index ff9feb93..574da070 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -159,6 +159,12 @@ def preset_with_subscription_overrides_tilda( "parent_preset_2 | parent_preset_1": { "~ test_2_1": { "current_override": "test_2_1", + "custom_list": [ + "elem1", + "elem2", + "elem3", + ], + "passed_list_elem": "{%contains_any('elem2', custom_list)}", } }, }, @@ -262,6 +268,8 @@ def test_subscription_overrides_tilda( assert sub_2_1.get("subscription_name").native == "test_2_1" assert sub_2_1.get("current_override").native == "test_2_1" # tilda sub takes precedence + assert sub_2_1.get("passed_list_elem").native is True + assert sub_2_1.get("custom_list").native == ["elem1", "elem2", "elem3"] def test_subscription_overrides_map( diff --git a/tests/unit/script/functions/test_string_functions.py b/tests/unit/script/functions/test_string_functions.py index cfa5175e..e5d898f8 100644 --- a/tests/unit/script/functions/test_string_functions.py +++ b/tests/unit/script/functions/test_string_functions.py @@ -118,6 +118,14 @@ class TestNumericFunctions: output = single_variable_output(f"{{%contains('a brown dog', '{value}')}}") assert output == expected_output + @pytest.mark.parametrize( + "value, expected_output", + [("['a', 'b', 'c']", True), ("['nope', [], {}]", False), ("['at', 'dog']", True)], + ) + def test_contains_any(self, value, expected_output): + output = single_variable_output(f"{{%contains_any('a brown dog', {value})}}") + assert output == expected_output + @pytest.mark.parametrize( "input_string, split, max_split, expected_output", [