diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index 5f405ed5..ab18ad6f 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -8,6 +8,7 @@ from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.utils.exceptions import UNREACHABLE from ytdl_sub.script.utils.exceptions import CycleDetected +from ytdl_sub.script.utils.exceptions import VariableDoesNotExist # pylint: disable=missing-raises-doc @@ -38,6 +39,11 @@ class Script: deps: List[str], ) -> None: for dep in variable_dependency.variables: + if dep.name not in self._variables: + raise VariableDoesNotExist( + f"{variable_name} uses the variable {dep.name} which does not exist." + ) + if variable_name in deps + [dep.name]: cycle_deps = [variable_name] + deps + [dep.name] cycle_deps_str = " -> ".join(cycle_deps) diff --git a/src/ytdl_sub/script/utils/exceptions.py b/src/ytdl_sub/script/utils/exceptions.py index 39f9cb79..b05976d4 100644 --- a/src/ytdl_sub/script/utils/exceptions.py +++ b/src/ytdl_sub/script/utils/exceptions.py @@ -19,6 +19,10 @@ class FunctionDoesNotExist(UserException): """Tried to use a function that does not exist""" +class VariableDoesNotExist(UserException): + """Tried to use a variable that does not exist""" + + class CycleDetected(UserException): """A cycle exists within a user's script""" diff --git a/tests/unit/script/types/test_string.py b/tests/unit/script/types/test_string.py index e9564227..581740c7 100644 --- a/tests/unit/script/types/test_string.py +++ b/tests/unit/script/types/test_string.py @@ -5,7 +5,6 @@ import pytest from ytdl_sub.script.parser import STRINGS_NOT_CLOSED from ytdl_sub.script.parser import STRINGS_ONLY_ARGS from ytdl_sub.script.script import Script -from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.utils.exceptions import InvalidSyntaxException diff --git a/tests/unit/script/types/test_variable.py b/tests/unit/script/types/test_variable.py new file mode 100644 index 00000000..91f47f6c --- /dev/null +++ b/tests/unit/script/types/test_variable.py @@ -0,0 +1,52 @@ +import re + +import pytest + +from ytdl_sub.script.script import Script +from ytdl_sub.script.types.resolvable import String +from ytdl_sub.script.utils.exceptions import CycleDetected +from ytdl_sub.script.utils.exceptions import VariableDoesNotExist + + +class TestVariable: + def test_simple(self): + assert Script({"a": "a", "b": "{b_}", "b_": "b"}).resolve() == { + "a": String("a"), + "b": String("b"), + "b_": String("b"), + } + + def test_multiple_variables(self): + assert Script({"a": "a", "b": "b", "b_": " {a} {b} "}).resolve() == { + "a": String("a"), + "b": String("b"), + "b_": String(" a b "), + } + + def test_simple_with_function(self): + assert Script({"a": "a", "b": "{%capitalize(b_)}", "b_": "b"}).resolve() == { + "a": String("a"), + "b": String("B"), + "b_": String("b"), + } + + def test_simple_cycle(self): + with pytest.raises( + CycleDetected, + match=re.escape("Cycle detected within these variables: a -> b -> a"), + ): + Script({"a": "{b}", "b": "{a}"}).resolve() + + def test_simple_cycle_with_function(self): + with pytest.raises( + CycleDetected, + match=re.escape("Cycle detected within these variables: b -> b_ -> b"), + ): + Script({"b": "{%capitalize(b_)}", "b_": "{b}"}).resolve() + + def test_undefined_variable(self): + with pytest.raises( + VariableDoesNotExist, + match=re.escape("b uses the variable c which does not exist."), + ): + Script({"a": "a", "b": "{c}"}).resolve()