diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index db98ab06..024823ec 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -8,6 +8,8 @@ import mergedeep from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.variables.override_variables import SUBSCRIPTION_NAME +from ytdl_sub.entries.variables.override_variables import OverrideVariables +from ytdl_sub.script.functions import Functions from ytdl_sub.script.parser import parse from ytdl_sub.script.script import Script from ytdl_sub.utils.script import ScriptUtils @@ -53,6 +55,28 @@ class Overrides(DictFormatterValidator, Scriptable): DictFormatterValidator.__init__(self, name, value) Scriptable.__init__(self) + for key in self._keys: + if OverrideVariables.is_override_variable_name(key): + raise self._validation_exception( + f"Override variable with name {key} cannot be used since it is a" + " built-in ytdl-sub override variable name." + ) + + if key in self.script.variable_names: + raise self._validation_exception( + f"Override variable with name {key} cannot be used since it is a" + " built-in ytdl-sub entry variable name." + ) + + if key in self.script.function_names or ( + key.startswith("%") and Functions.is_built_in(key[1:]) + ): + raise self._validation_exception( + f"Override function definition with name {key} cannot be used since it is a" + " built-in ytdl-sub function name." + ) + + self.unresolvable.add(VARIABLES.entry_metadata.variable_name) def initial_variables( diff --git a/src/ytdl_sub/entries/variables/override_variables.py b/src/ytdl_sub/entries/variables/override_variables.py index 8a885f83..4bb75e1a 100644 --- a/src/ytdl_sub/entries/variables/override_variables.py +++ b/src/ytdl_sub/entries/variables/override_variables.py @@ -1,5 +1,9 @@ +import re + SUBSCRIPTION_NAME = "subscription_name" SUBSCRIPTION_VALUE = "subscription_value" +SUBSCRIPTION_MAP = "subscription_map" +SUBSCRIPTION_ARRAY = "subscription_array" class OverrideVariables: @@ -55,3 +59,26 @@ class OverrideVariables: ``subscription_value``. """ return f"subscription_value_{index + 1}" + + @classmethod + def is_override_variable_name(cls, variable_name: str): + """ + Returns + ------- + True of a variable name collides with an override variable name. False otherwise. + """ + if variable_name in ( + SUBSCRIPTION_NAME, + SUBSCRIPTION_VALUE, + SUBSCRIPTION_MAP, + SUBSCRIPTION_ARRAY, + ): + return True + + if re.fullmatch(r"^subscription_value_(\d+)$", variable_name): + return True + + if re.fullmatch(r"^subscription_indent_(\d+)$", variable_name): + return True + + return False diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index c99d3b5d..2747f003 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -31,6 +31,13 @@ def _function_name(function_key: str) -> str: return function_key[1:] +def _to_function_definition_name(function_key: str) -> str: + """ + Add % in %custom_function + """ + return f"%{function_key}" + + class Script: """ Takes a dictionary of both @@ -455,3 +462,23 @@ class Script: return resolvable raise RuntimeException(f"Tried to get unresolved variable {variable_name}") + + @property + def variable_names(self) -> Set[str]: + """ + Returns + ------- + Set[str] + Names of all the variables within the Script. + """ + return set(list(self._variables.keys())) + + @property + def function_names(self) -> Set[str]: + """ + Returns + ------- + Set[str] + Names of all functions within the Script. + """ + return set(_to_function_definition_name(name) for name in self._functions.keys()) diff --git a/tests/unit/config/test_preset.py b/tests/unit/config/test_preset.py index f8a984fa..ee5e6d02 100644 --- a/tests/unit/config/test_preset.py +++ b/tests/unit/config/test_preset.py @@ -207,3 +207,84 @@ class TestPreset: "output_options": output_options, }, ) + + @pytest.mark.parametrize( + "override_variable_name", ["subscription_name", "subscription_value_3", "subscription_map"] + ) + def test_preset_error_override_variable_collides_with_override( + self, config_file, output_options, youtube_video, override_variable_name: str + ): + with pytest.raises( + ValidationException, + match=re.escape( + f"Override variable with name {override_variable_name} cannot be used since" + " it is a built-in ytdl-sub override variable name." + ), + ): + _ = Preset( + config=config_file, + name="test", + value={ + "download": youtube_video, + "output_options": {"output_directory": "dir", "file_name": "{dne_var}"}, + "overrides": {override_variable_name: "fail"}, + }, + ) + + @pytest.mark.parametrize( + "entry_variable_name", + [ + "title", + "playlist_uid", + "source_title", + "playlist_max_upload_year", + ], + ) + def test_preset_error_override_variable_collides_with_entry_variable( + self, config_file, output_options, youtube_video, entry_variable_name: str + ): + with pytest.raises( + ValidationException, + match=re.escape( + f"Override variable with name {entry_variable_name} cannot be used since" + " it is a built-in ytdl-sub entry variable name." + ), + ): + _ = Preset( + config=config_file, + name="test", + value={ + "download": youtube_video, + "output_options": {"output_directory": "dir", "file_name": "{dne_var}"}, + "overrides": {entry_variable_name: "fail"}, + }, + ) + + @pytest.mark.parametrize( + "function_name", + [ + "%extract_field_from_siblings", + "%extract_field_from_metadata_array", + "%sanitize", + "%array" + ], + ) + def test_preset_error_override_variable_collides_with_custom_function( + self, config_file, output_options, youtube_video, function_name: str + ): + with pytest.raises( + ValidationException, + match=re.escape( + f"Override function definition with name {function_name} cannot be used since" + " it is a built-in ytdl-sub function name." + ), + ): + _ = Preset( + config=config_file, + name="test", + value={ + "download": youtube_video, + "output_options": {"output_directory": "dir", "file_name": "{dne_var}"}, + "overrides": {function_name: "fail"}, + }, + ) diff --git a/tests/unit/entries/test_override_variables.py b/tests/unit/entries/test_override_variables.py new file mode 100644 index 00000000..ce30db9f --- /dev/null +++ b/tests/unit/entries/test_override_variables.py @@ -0,0 +1,35 @@ +import pytest + +from ytdl_sub.entries.variables.override_variables import OverrideVariables + + +class TestOverrideVariables: + @pytest.mark.parametrize( + "override_variable_name", + [ + "subscription_value", + "subscription_name", + "subscription_map", + "subscription_array", + "subscription_value_532", + "subscription_indent_0", + "subscription_indent_1", + ], + ) + def test_override_variables_contains(self, override_variable_name: str): + assert OverrideVariables.is_override_variable_name(override_variable_name) + + @pytest.mark.parametrize( + "override_variable_name", + [ + "subscription_value_var", + "subscription_name_var", + "subscription_map_var", + "subscription_array_var", + "subscription_value_532_var", + "subscription_indent_0_var", + "subscription_indent_1_var", + ], + ) + def test_override_variables_does_not_contains(self, override_variable_name: str): + assert not OverrideVariables.is_override_variable_name(override_variable_name)