ScriptOutput and partial script
This commit is contained in:
parent
4008746021
commit
2bc6f6adff
15 changed files with 342 additions and 118 deletions
|
|
@ -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)
|
||||
|
|
|
|||
25
src/ytdl_sub/script/script_output.py
Normal file
25
src/ytdl_sub/script/script_output.py
Normal file
|
|
@ -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])
|
||||
|
|
@ -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"""
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ def single_variable_output(script: str):
|
|||
}
|
||||
)
|
||||
.resolve(update=True)
|
||||
.get("output")
|
||||
.native
|
||||
.get_native("output")
|
||||
)
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
54
tests/unit/script/test_script_builder.py
Normal file
54
tests/unit/script/test_script_builder.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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"]')}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue