diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index 12c2ee0a..727f29d1 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -6,21 +6,36 @@ from typing import Set from ytdl_sub.script.functions import Functions from ytdl_sub.script.parser import parse +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.resolvable import Lambda from ytdl_sub.script.types.resolvable import Resolvable 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 FunctionDoesNotExist from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArguments from ytdl_sub.script.utils.exceptions import RuntimeException +from ytdl_sub.script.utils.exceptions import ScriptBuilderMissingDefinitions +from ytdl_sub.script.utils.exceptions import VariableDoesNotExist from ytdl_sub.script.utils.name_validation import validate_variable_name from ytdl_sub.script.utils.type_checking import FunctionSpec # pylint: disable=missing-raises-doc +def _is_function(override_name: str): + return override_name.startswith("%") + + +def _function_name(function_key: str) -> str: + """ + Drop the % in %custom_function + """ + return function_key[1:] + + class Script: """ Takes a dictionary of both @@ -29,17 +44,6 @@ class Script: ``{ %custom_function: syntax }`` """ - @classmethod - def _is_function(cls, override_name: str): - return override_name.startswith("%") - - @classmethod - def _function_name(cls, function_key: str) -> str: - """ - Drop the % in %custom_function - """ - return function_key[1:] - def _ensure_no_cycle( self, name: str, dep: str, deps: List[str], definitions: Dict[str, SyntaxTree] ): @@ -205,23 +209,23 @@ class Script: def __init__(self, script: Dict[str, str]): function_names: Set[str] = { - self._function_name(name) for name in script.keys() if self._is_function(name) + _function_name(name) for name in script.keys() if _is_function(name) } variable_names: Set[str] = { - validate_variable_name(name) for name in script.keys() if not self._is_function(name) + validate_variable_name(name) for name in script.keys() if not _is_function(name) } self._functions: Dict[str, SyntaxTree] = { # custom_function_name must be passed to properly type custom function # arguments uniquely if they're nested (i.e. $0 to $custom_func___0) - self._function_name(function_key): parse( + _function_name(function_key): parse( text=function_value, - name=self._function_name(function_key), + name=_function_name(function_key), custom_function_names=function_names, variable_names=variable_names, ) for function_key, function_value in script.items() - if self._is_function(function_key) + if _is_function(function_key) } self._variables: Dict[str, SyntaxTree] = { @@ -232,7 +236,7 @@ class Script: variable_names=variable_names, ) for variable_key, variable_value in script.items() - if not self._is_function(variable_key) + if not _is_function(variable_key) } self._validate() @@ -245,7 +249,7 @@ class Script: resolved: Optional[Dict[str, Resolvable]] = None, unresolvable: Optional[Set[str]] = None, update: bool = False, - ) -> Dict[str, Resolvable]: + ) -> ScriptOutput: """ Parameters ---------- @@ -302,7 +306,7 @@ class Script: if update: self._update_internally(resolved_variables=resolved_variables) - return resolved_variables + return ScriptOutput(resolved_variables) def add(self, variables: Dict[str, str]) -> "Script": for variable_name, variable_definition in variables.items(): @@ -325,3 +329,99 @@ class Script: return resolvable raise RuntimeException(f"Tried to get unresolved variable {variable_name}") + + +class ScriptBuilder: + """ + Takes a dictionary of both + ``{ variable_names: syntax }`` + and + ``{ %custom_function: syntax }`` + """ + + def __init__(self, script: Dict[str, str]): + self._functions: Dict[str, SyntaxTree] = { + # custom_function_name must be passed to properly type custom function + # arguments uniquely if they're nested (i.e. $0 to $custom_func___0) + _function_name(function_key): parse( + text=function_value, + name=_function_name(function_key), + ) + for function_key, function_value in script.items() + if _is_function(function_key) + } + + self._variables: Dict[str, SyntaxTree] = { + variable_key: parse( + text=variable_value, + name=variable_key, + ) + for variable_key, variable_value in script.items() + if not _is_function(variable_key) + } + + def add(self, variables: Dict[str, str]) -> "ScriptBuilder": + for variable_name, variable_definition in variables.items(): + self._variables[variable_name] = parse( + text=variable_definition, + name=variable_name, + ) + return self + + @property + def _missing_metadata(self) -> Dict[str, Set[str]]: + missing_metadata: Dict[str, Set[str]] = {} + + defined_variables: Set[str] = set(self._variables.keys()) + defined_functions: Set[str] = set(self._functions.keys()) + for name, variable in self._variables.items(): + missing_metadata[name] = {var.name for var in variable.variables}.difference( + defined_variables + ) + missing_metadata[name].update( + {fun.name for fun in variable.custom_functions}.difference(defined_functions) + ) + + for name, function in self._functions.items(): + missing_metadata[name] = {var.name for var in function.variables}.difference( + defined_variables + ) + missing_metadata[name].update( + {fun.name for fun in function.custom_functions}.difference(defined_functions) + ) + + return missing_metadata + + @classmethod + def _build(cls, variables: Dict[str, SyntaxTree], functions: Dict[str, SyntaxTree]) -> Script: + script = Script({}) + script._variables = variables + script._functions = functions + script._validate() + return script + + def partial_build(self) -> Script: + missing_metadata = self._missing_metadata + maybe_resolvable_variables: Dict[str, SyntaxTree] = { + name: variable + for name, variable in self._variables.items() + if name not in missing_metadata + } + maybe_resolvable_functions: Dict[str, SyntaxTree] = { + name: function + for name, function in self._functions.items() + if name not in missing_metadata + } + + return self._build( + variables=maybe_resolvable_variables, functions=maybe_resolvable_functions + ) + + def build(self) -> Script: + for name, missing_metadata in self._missing_metadata.items(): + if missing_metadata: + raise ScriptBuilderMissingDefinitions( + f"{name} is missing the following definitions: {', '.join(missing_metadata)}" + ) + + return self._build(variables=self._variables, functions=self._functions) diff --git a/src/ytdl_sub/script/script_output.py b/src/ytdl_sub/script/script_output.py new file mode 100644 index 00000000..834c9044 --- /dev/null +++ b/src/ytdl_sub/script/script_output.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass +from typing import Any +from typing import Dict + +from ytdl_sub.script.types.resolvable import Resolvable + + +@dataclass(frozen=True) +class ScriptOutput: + output: Dict[str, Resolvable] + + def as_resolvable(self) -> Dict[str, Resolvable]: + return self.output + + def as_native(self) -> Dict[str, Any]: + return {name: out.native for name, out in self.output.items()} + + def get(self, name: str) -> Resolvable: + return self.output[name] + + def get_native(self, name: str) -> Any: + return self.output[name].native + + def get_str(self, name: str) -> str: + return str(self.output[name]) diff --git a/src/ytdl_sub/script/utils/exceptions.py b/src/ytdl_sub/script/utils/exceptions.py index fdf95545..08eadaeb 100644 --- a/src/ytdl_sub/script/utils/exceptions.py +++ b/src/ytdl_sub/script/utils/exceptions.py @@ -42,6 +42,10 @@ class VariableDoesNotExist(UserException): """Tried to use a variable that does not exist""" +class ScriptBuilderMissingDefinitions(UserException): + """Tried to build an incomplete ScriptBuilder""" + + class CycleDetected(UserException): """A cycle exists within a user's script""" diff --git a/tests/unit/script/conftest.py b/tests/unit/script/conftest.py index 2b3e4f49..951f5aff 100644 --- a/tests/unit/script/conftest.py +++ b/tests/unit/script/conftest.py @@ -9,7 +9,6 @@ def single_variable_output(script: str): } ) .resolve(update=True) - .get("output") - .native + .get_native("output") ) return output diff --git a/tests/unit/script/test_script.py b/tests/unit/script/test_script.py index 36a335a7..0b86e2be 100644 --- a/tests/unit/script/test_script.py +++ b/tests/unit/script/test_script.py @@ -1,4 +1,5 @@ from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.map import Map from ytdl_sub.script.types.resolvable import String @@ -12,11 +13,13 @@ class TestScript: "bb": "b", "cc": "{%custom_func(aa, bb)}", } - ).resolve(resolved={"bb": String("bb_override")}) == { - "aa": String("a"), - "bb": String("bb_override"), - "cc": String('return ["a", "bb_override"]'), - } + ).resolve(resolved={"bb": String("bb_override")}) == ScriptOutput( + { + "aa": String("a"), + "bb": String("bb_override"), + "cc": String('return ["a", "bb_override"]'), + } + ) def test_partial_resolve(self): assert Script( @@ -26,7 +29,7 @@ class TestScript: "bb": "b", "cc": "{%custom_func(aa, bb)}", } - ).resolve(unresolvable={"bb"}) == {"aa": String("a")} + ).resolve(unresolvable={"bb"}) == ScriptOutput({"aa": String("a")}) def test_partial_update_script(self): # to be resolved later diff --git a/tests/unit/script/test_script_builder.py b/tests/unit/script/test_script_builder.py new file mode 100644 index 00000000..87f06b3f --- /dev/null +++ b/tests/unit/script/test_script_builder.py @@ -0,0 +1,54 @@ +import re + +import pytest + +from ytdl_sub.script.script import Script +from ytdl_sub.script.script import ScriptBuilder +from ytdl_sub.script.types.map import Map +from ytdl_sub.script.types.resolvable import String +from ytdl_sub.script.utils.exceptions import ScriptBuilderMissingDefinitions +from ytdl_sub.script.utils.exceptions import VariableDoesNotExist + + +class TestScriptBuilder: + def test_partial_update_script(self): + # to be resolved later + entry_map = Map({String("title"): String("the title")}) + + script = ScriptBuilder( + { + "entry": "{ {} }", + "title": "{%map_get(entry, 'title')}", + "resolved_override": "{override} mom", + } + ) + + assert script.partial_build().resolve() + + with pytest.raises( + ScriptBuilderMissingDefinitions, + match=re.escape("resolved_override is missing the following definitions: override"), + ): + script.build() + + script.add({"override": "hi"}) + + script.build() + + # script.resolve(unresolvable={"entry"}, update=True) + # assert script.get("override") == String("hi") + # assert script.get("resolved_override") == String("hi mom") + # + # script.add( + # { + # "new_variable_titlecase": "{%titlecase(new_variable_upper)}", + # "new_variable": "{resolved_override} {title}", + # "new_variable_upper": "{%upper(new_variable)}", + # } + # ).resolve(resolved={"entry": entry_map}, update=True) + # + # assert script.get("title") == String("the title") + # assert script.get("new_variable") == String("hi mom the title") + # assert script.get("new_variable_upper") == String("HI MOM THE TITLE") + # assert script.get("new_variable_titlecase") == String("Hi Mom The Title") + # assert script.get("entry") == entry_map diff --git a/tests/unit/script/types/test_array.py b/tests/unit/script/types/test_array.py index 3b8fe1e2..837f5217 100644 --- a/tests/unit/script/types/test_array.py +++ b/tests/unit/script/types/test_array.py @@ -6,6 +6,7 @@ from ytdl_sub.script.parser import _UNEXPECTED_CHAR_ARGUMENT from ytdl_sub.script.parser import _UNEXPECTED_COMMA_ARGUMENT from ytdl_sub.script.parser import ParsedArgType from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import String @@ -14,33 +15,35 @@ from ytdl_sub.script.utils.exceptions import InvalidSyntaxException class TestArray: def test_return(self): - assert Script({"array": "{['a', 3.14]}"}).resolve() == { - "array": Array([String("a"), Float(3.14)]) - } + assert Script({"array": "{['a', 3.14]}"}).resolve() == ScriptOutput( + {"array": Array([String("a"), Float(3.14)])} + ) def test_return_as_str(self): - assert Script({"array": "str: {['a', 3.14]}"}).resolve() == { - "array": String('str: ["a", 3.14]') - } + assert Script({"array": "str: {['a', 3.14]}"}).resolve() == ScriptOutput( + {"array": String('str: ["a", 3.14]')} + ) def test_nested_array(self): assert Script( {"array": "{['level1', ['level2', ['level3', 'level3'], 'level2'], 'level1']}"} - ).resolve() == { - "array": Array( - [ - String("level1"), - Array( - [ - String("level2"), - Array([String("level3"), String("level3")]), - String("level2"), - ], - ), - String("level1"), - ] - ) - } + ).resolve() == ScriptOutput( + { + "array": Array( + [ + String("level1"), + Array( + [ + String("level2"), + Array([String("level3"), String("level3")]), + String("level2"), + ], + ), + String("level1"), + ] + ) + } + ) @pytest.mark.parametrize( "array", @@ -52,7 +55,7 @@ class TestArray: ], ) def test_empty(self, array: str): - assert Script({"array": array}).resolve() == {"array": Array([])} + assert Script({"array": array}).resolve() == ScriptOutput({"array": Array([])}) @pytest.mark.parametrize( "array", @@ -111,4 +114,6 @@ class TestArray: "bb": "b", "cc": "{%custom_func(aa, bb)}", } - ).resolve() == {"aa": String("a"), "bb": String("b"), "cc": String('return ["a", "b"]')} + ).resolve() == ScriptOutput( + {"aa": String("a"), "bb": String("b"), "cc": String('return ["a", "b"]')} + ) diff --git a/tests/unit/script/types/test_bool.py b/tests/unit/script/types/test_bool.py index 255a095e..5de7ddb1 100644 --- a/tests/unit/script/types/test_bool.py +++ b/tests/unit/script/types/test_bool.py @@ -4,6 +4,7 @@ import pytest from ytdl_sub.script.parser import BOOLEAN_ONLY_ARGS from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.utils.exceptions import InvalidSyntaxException @@ -33,10 +34,14 @@ class TestBool: ], ) def test_boolean(self, boolean: bool, expected_boolean: bool): - assert Script({"boolean": boolean, "as_string": "{%string(boolean)}"}).resolve() == { - "boolean": Boolean(expected_boolean), - "as_string": String(str(expected_boolean)), - } + assert Script( + {"boolean": boolean, "as_string": "{%string(boolean)}"} + ).resolve() == ScriptOutput( + { + "boolean": Boolean(expected_boolean), + "as_string": String(str(expected_boolean)), + } + ) @pytest.mark.parametrize( "to_cast, expected_bool", @@ -56,4 +61,6 @@ class TestBool: ], ) def test_cast_as_bool(self, to_cast: str, expected_bool: bool): - assert Script({"as_bool": to_cast}).resolve() == {"as_bool": Boolean(expected_bool)} + assert Script({"as_bool": to_cast}).resolve() == ScriptOutput( + {"as_bool": Boolean(expected_bool)} + ) diff --git a/tests/unit/script/types/test_custom_function.py b/tests/unit/script/types/test_custom_function.py index ed59fa05..4e846a1b 100644 --- a/tests/unit/script/types/test_custom_function.py +++ b/tests/unit/script/types/test_custom_function.py @@ -4,6 +4,7 @@ import pytest from ytdl_sub.script.parser import CUSTOM_FUNCTION_ARGUMENTS_ONLY_ARGS from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.utils.exceptions import CycleDetected from ytdl_sub.script.utils.exceptions import FunctionDoesNotExist @@ -19,7 +20,7 @@ class TestCustomFunction: "%custom_square": "{%mul($0, $0)}", "output": "{%custom_square(3)}", } - ).resolve() == {"output": Integer(9)} + ).resolve() == ScriptOutput({"output": Integer(9)}) def test_custom_function_cycle(self): with pytest.raises( diff --git a/tests/unit/script/types/test_float.py b/tests/unit/script/types/test_float.py index 1328c5b1..4056488a 100644 --- a/tests/unit/script/types/test_float.py +++ b/tests/unit/script/types/test_float.py @@ -5,6 +5,7 @@ import pytest from ytdl_sub.script.parser import NUMERICS_INVALID_CHAR from ytdl_sub.script.parser import NUMERICS_ONLY_ARGS from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import String @@ -39,10 +40,12 @@ class TestFloat: ], ) def test_float(self, float_: str, expected_float: int): - assert Script({"out": float_, "as_string": "{%string(out)}"}).resolve() == { - "out": Float(expected_float), - "as_string": String(str(expected_float)), - } + assert Script({"out": float_, "as_string": "{%string(out)}"}).resolve() == ScriptOutput( + { + "out": Float(expected_float), + "as_string": String(str(expected_float)), + } + ) @pytest.mark.parametrize( "float_", @@ -71,4 +74,6 @@ class TestFloat: ], ) def test_cast_as_float(self, to_cast: str, expected_float: float): - assert Script({"as_float": to_cast}).resolve() == {"as_float": Float(expected_float)} + assert Script({"as_float": to_cast}).resolve() == ScriptOutput( + {"as_float": Float(expected_float)} + ) diff --git a/tests/unit/script/types/test_integer.py b/tests/unit/script/types/test_integer.py index 8e49d722..18747e11 100644 --- a/tests/unit/script/types/test_integer.py +++ b/tests/unit/script/types/test_integer.py @@ -5,6 +5,7 @@ import pytest from ytdl_sub.script.parser import NUMERICS_INVALID_CHAR from ytdl_sub.script.parser import NUMERICS_ONLY_ARGS from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.types.resolvable import String @@ -39,10 +40,14 @@ class TestInteger: ], ) def test_integer(self, integer: str, expected_integer: int): - assert Script({"integer": integer, "as_string": "{%string(integer)}"}).resolve() == { - "integer": Integer(expected_integer), - "as_string": String(str(expected_integer)), - } + assert Script( + {"integer": integer, "as_string": "{%string(integer)}"} + ).resolve() == ScriptOutput( + { + "integer": Integer(expected_integer), + "as_string": String(str(expected_integer)), + } + ) @pytest.mark.parametrize( "integer", @@ -71,4 +76,6 @@ class TestInteger: ], ) def test_cast_as_integer(self, to_cast: str, expected_int: int): - assert Script({"as_int": to_cast}).resolve() == {"as_int": Integer(expected_int)} + assert Script({"as_int": to_cast}).resolve() == ScriptOutput( + {"as_int": Integer(expected_int)} + ) diff --git a/tests/unit/script/types/test_lambda_function.py b/tests/unit/script/types/test_lambda_function.py index 9eae411a..379c2207 100644 --- a/tests/unit/script/types/test_lambda_function.py +++ b/tests/unit/script/types/test_lambda_function.py @@ -3,6 +3,7 @@ import re import pytest from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments @@ -12,7 +13,7 @@ class TestLambdaFunction: def test_lambda_with_custom_function(self): assert Script( {"%times_two": "{%mul($0, 2)}", "wip": "{%array_apply([1, 2, 3], %times_two)}"} - ).resolve() == {"wip": Array([Integer(2), Integer(4), Integer(6)])} + ).resolve() == ScriptOutput({"wip": Array([Integer(2), Integer(4), Integer(6)])}) def test_conditional_lambda_with_custom_functions(self): assert Script( @@ -21,7 +22,7 @@ class TestLambdaFunction: "%times_two": "{%mul($0, 2)}", "wip": "{%array_apply([1, 2, 3], %if(False, %times_two, %times_three))}", } - ).resolve() == {"wip": Array([Integer(3), Integer(6), Integer(9)])} + ).resolve() == ScriptOutput({"wip": Array([Integer(3), Integer(6), Integer(9)])}) def test_nested_custom_functions(self): assert Script( @@ -30,7 +31,7 @@ class TestLambdaFunction: "%times_two": "{%mul($0, 2)}", "identity": "{%times_three(%times_two(1))}", } - ).resolve() == {"identity": Integer(6)} + ).resolve() == ScriptOutput({"identity": Integer(6)}) def test_nested_custom_functions_within_custom_functions(self): assert Script( @@ -40,7 +41,7 @@ class TestLambdaFunction: "%power_4": "{%mul(%power_3($0), 2)}", "power_of_4": "{%power_4(2)}", } - ).resolve() == {"power_of_4": Integer(16)} + ).resolve() == ScriptOutput({"power_of_4": Integer(16)}) def test_nested_lambda_custom_functions_within_custom_functions(self): assert Script( @@ -51,7 +52,7 @@ class TestLambdaFunction: "%nest1": "{%array_at(%array_apply([$0], %nest2), 0)}", "output": "{%array_at(%array_apply([2], %nest1), 0)}", } - ).resolve() == {"output": Integer(4)} + ).resolve() == ScriptOutput({"output": Integer(4)}) class TestLambdaFunctionIncompatibleNumArguments: diff --git a/tests/unit/script/types/test_map.py b/tests/unit/script/types/test_map.py index 607cf335..10faa962 100644 --- a/tests/unit/script/types/test_map.py +++ b/tests/unit/script/types/test_map.py @@ -10,6 +10,7 @@ from ytdl_sub.script.parser import MAP_KEY_WITH_NO_VALUE from ytdl_sub.script.parser import MAP_MISSING_KEY from ytdl_sub.script.parser import ParsedArgType from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.map import Map from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import String @@ -19,14 +20,14 @@ from ytdl_sub.script.utils.exceptions import KeyNotHashableRuntimeException class TestMap: def test_return(self): - assert Script({"map": "{{'a': 3.14}}"}).resolve() == { - "map": Map({String("a"): Float(3.14)}) - } + assert Script({"map": "{{'a': 3.14}}"}).resolve() == ScriptOutput( + {"map": Map({String("a"): Float(3.14)})} + ) def test_return_as_str(self): - assert Script({"map": "json: {{'a': 3.14}}"}).resolve() == { - "map": String('json: {"a": 3.14}') - } + assert Script({"map": "json: {{'a': 3.14}}"}).resolve() == ScriptOutput( + {"map": String('json: {"a": 3.14}')} + ) def test_nested_map(self): map_str = """{ @@ -44,26 +45,28 @@ class TestMap: } }""" - assert Script({"map": map_str}).resolve() == { - "map": Map( - { - String("level1"): Map( - { - String("level2"): Map( - { - String("level3"): Map( - {String("level4_key"): String("level4_value")} - ), - String("level3_key"): String("level3_value"), - } - ), - String("level2_key"): String("level2_value"), - } - ), - String("level1_key"): String("level1_value"), - } - ) - } + assert Script({"map": map_str}).resolve() == ScriptOutput( + { + "map": Map( + { + String("level1"): Map( + { + String("level2"): Map( + { + String("level3"): Map( + {String("level4_key"): String("level4_value")} + ), + String("level3_key"): String("level3_value"), + } + ), + String("level2_key"): String("level2_value"), + } + ), + String("level1_key"): String("level1_value"), + } + ) + } + ) @pytest.mark.parametrize( "empty_map", @@ -75,7 +78,7 @@ class TestMap: ], ) def test_empty_map(self, empty_map: str): - assert Script({"map": empty_map}).resolve() == {"map": Map({})} + assert Script({"map": empty_map}).resolve() == ScriptOutput({"map": Map({})}) @pytest.mark.parametrize( "map", @@ -166,10 +169,12 @@ class TestMap: "map": "{{key_variable : 'value' }}", "key_variable": "hashable", } - ).resolve() == { - "key_variable": String("hashable"), - "map": Map({String("hashable"): String("value")}), - } + ).resolve() == ScriptOutput( + { + "key_variable": String("hashable"), + "map": Map({String("hashable"): String("value")}), + } + ) def test_map_key_is_non_hashable_variable(self): with pytest.raises( diff --git a/tests/unit/script/types/test_string.py b/tests/unit/script/types/test_string.py index 6fc990ba..1e5b5d1b 100644 --- a/tests/unit/script/types/test_string.py +++ b/tests/unit/script/types/test_string.py @@ -5,6 +5,7 @@ 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.script_output import ScriptOutput from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.utils.exceptions import InvalidSyntaxException @@ -46,7 +47,7 @@ class TestString: ], ) def test_string(self, string: str, expected_string: str): - assert Script({"out": string}).resolve() == {"out": String(expected_string)} + assert Script({"out": string}).resolve() == ScriptOutput({"out": String(expected_string)}) @pytest.mark.parametrize( "string", diff --git a/tests/unit/script/types/test_variable.py b/tests/unit/script/types/test_variable.py index 59eb6f66..526e1476 100644 --- a/tests/unit/script/types/test_variable.py +++ b/tests/unit/script/types/test_variable.py @@ -3,6 +3,7 @@ import re import pytest from ytdl_sub.script.script import Script +from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.utils.exceptions import CycleDetected from ytdl_sub.script.utils.exceptions import InvalidVariableName @@ -11,25 +12,31 @@ 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"), - } + assert Script({"a": "a", "b": "{b_}", "b_": "b"}).resolve() == ScriptOutput( + { + "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 "), - } + assert Script({"a": "a", "b": "b", "b_": " {a} {b} "}).resolve() == ScriptOutput( + { + "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"), - } + assert Script({"a": "a", "b": "{%capitalize(b_)}", "b_": "b"}).resolve() == ScriptOutput( + { + "a": String("a"), + "b": String("B"), + "b_": String("b"), + } + ) def test_simple_cycle(self): with pytest.raises(