kinda working
This commit is contained in:
parent
f22a072ff2
commit
13a37b6453
9 changed files with 100 additions and 34 deletions
|
|
@ -52,7 +52,7 @@ presets:
|
||||||
|
|
||||||
# Creates an array in the form of [ { url: ..., category: ..., metadata_field_1: ... }, ... ]
|
# Creates an array in the form of [ { url: ..., category: ..., metadata_field_1: ... }, ... ]
|
||||||
category_url_array: >-
|
category_url_array: >-
|
||||||
{ %map_apply( subscription_dict, %flat_array__category_to_map_format ) }
|
{ %array_flatten( %map_apply( subscription_dict, %flat_array__category_to_map_format ) ) }
|
||||||
|
|
||||||
# Creates a map in the form of { <url value>: { category: ..., metadata_field_1: ... }, ... }
|
# Creates a map in the form of { <url value>: { category: ..., metadata_field_1: ... }, ... }
|
||||||
category_url_map: >-
|
category_url_map: >-
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,6 @@ presets:
|
||||||
- "_music_video_extras_base"
|
- "_music_video_extras_base"
|
||||||
- "_music_video_tags"
|
- "_music_video_tags"
|
||||||
|
|
||||||
overrides:
|
# overrides:
|
||||||
metadata_verify_plex_suffix: "{url_metadata}"
|
# metadata_verify_plex_suffix: "{url_metadata}"
|
||||||
music_video_file_name_suffix: "-{music_video_album}"
|
# music_video_file_name_suffix: "-{music_video_album}"
|
||||||
|
|
@ -18,8 +18,8 @@ class ConditionalFunctions:
|
||||||
depending on the ``condition`` value.
|
depending on the ``condition`` value.
|
||||||
"""
|
"""
|
||||||
if condition.value:
|
if condition.value:
|
||||||
return true
|
return true.value()
|
||||||
return false
|
return false.value()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def elif_(*if_elif_else: AnyArgument) -> AnyArgument:
|
def elif_(*if_elif_else: AnyArgument) -> AnyArgument:
|
||||||
|
|
@ -50,9 +50,9 @@ class ConditionalFunctions:
|
||||||
|
|
||||||
for idx in range(0, len(arguments) - 1, 2):
|
for idx in range(0, len(arguments) - 1, 2):
|
||||||
if bool(arguments[idx].value):
|
if bool(arguments[idx].value):
|
||||||
return arguments[idx + 1]
|
return arguments[idx + 1].value()
|
||||||
|
|
||||||
return arguments[-1]
|
return arguments[-1].value()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def if_passthrough(
|
def if_passthrough(
|
||||||
|
|
@ -63,6 +63,7 @@ class ConditionalFunctions:
|
||||||
Conditional ``if`` statement that returns the ``maybe_true_arg`` if it evaluates to True,
|
Conditional ``if`` statement that returns the ``maybe_true_arg`` if it evaluates to True,
|
||||||
otherwise returns ``else_arg``.
|
otherwise returns ``else_arg``.
|
||||||
"""
|
"""
|
||||||
if bool(maybe_true_arg.value):
|
maybe_true_value = maybe_true_arg.value()
|
||||||
return maybe_true_arg
|
if bool(maybe_true_value):
|
||||||
return else_arg
|
return maybe_true_value
|
||||||
|
return else_arg.value()
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from typing import Set
|
||||||
from ytdl_sub.script.functions import Functions
|
from ytdl_sub.script.functions import Functions
|
||||||
from ytdl_sub.script.parser import parse
|
from ytdl_sub.script.parser import parse
|
||||||
from ytdl_sub.script.script_output import ScriptOutput
|
from ytdl_sub.script.script_output import ScriptOutput
|
||||||
from ytdl_sub.script.types.resolvable import Lambda
|
from ytdl_sub.script.types.resolvable import Lambda, BuiltInFunctionType
|
||||||
from ytdl_sub.script.types.resolvable import Resolvable
|
from ytdl_sub.script.types.resolvable import Resolvable
|
||||||
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
from ytdl_sub.script.types.syntax_tree import SyntaxTree
|
||||||
from ytdl_sub.script.types.variable import FunctionArgument
|
from ytdl_sub.script.types.variable import FunctionArgument
|
||||||
|
|
@ -146,20 +146,34 @@ class Script:
|
||||||
self, prefix: str, name: str, definition: SyntaxTree
|
self, prefix: str, name: str, definition: SyntaxTree
|
||||||
):
|
):
|
||||||
for function in definition.built_in_functions:
|
for function in definition.built_in_functions:
|
||||||
spec = FunctionSpec.from_callable(Functions.get(function.name))
|
for arg in function.args:
|
||||||
|
self._ensure_lambda_usage_num_input_arguments_valid(
|
||||||
|
prefix=prefix, name=name, definition=SyntaxTree([arg])
|
||||||
|
)
|
||||||
|
|
||||||
|
spec = FunctionSpec.from_callable(
|
||||||
|
name=function.name, callable_ref=Functions.get(function.name)
|
||||||
|
)
|
||||||
if not (lambda_type := spec.is_lambda_like):
|
if not (lambda_type := spec.is_lambda_like):
|
||||||
return
|
return
|
||||||
|
|
||||||
lambda_function_names = set(
|
lambda_function_names: Set[str] = set()
|
||||||
lamb.value
|
for lamb in SyntaxTree(function.args).lambdas:
|
||||||
for lamb in SyntaxTree(function.args).lambdas
|
if lamb in function.args:
|
||||||
if isinstance(lamb, Lambda) and lamb in function.args
|
lambda_function_names.add(lamb.value)
|
||||||
)
|
|
||||||
|
# See if the arg outputs a lambda (from an if).
|
||||||
|
# If so, add the possible lambda to be checked
|
||||||
|
for arg in function.args:
|
||||||
|
if isinstance(arg, BuiltInFunctionType) and arg.output_type() == Lambda and lamb in arg.args:
|
||||||
|
lambda_function_names.add(lamb.value)
|
||||||
|
|
||||||
# Only case len(lambda_function_names) > 1 is when used in if-statements
|
# Only case len(lambda_function_names) > 1 is when used in if-statements
|
||||||
for lambda_function_name in lambda_function_names:
|
for lambda_function_name in lambda_function_names:
|
||||||
if Functions.is_built_in(lambda_function_name):
|
if Functions.is_built_in(lambda_function_name):
|
||||||
lambda_spec = FunctionSpec.from_callable(Functions.get(lambda_function_name))
|
lambda_spec = FunctionSpec.from_callable(
|
||||||
|
name=lambda_function_name, callable_ref=Functions.get(lambda_function_name)
|
||||||
|
)
|
||||||
if not lambda_spec.is_num_args_compatible(lambda_type.num_input_args()):
|
if not lambda_spec.is_num_args_compatible(lambda_type.num_input_args()):
|
||||||
expected_args_str = str(lambda_spec.num_required_args)
|
expected_args_str = str(lambda_spec.num_required_args)
|
||||||
if lambda_spec.num_required_args != len(lambda_spec.args):
|
if lambda_spec.num_required_args != len(lambda_spec.args):
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ class BuiltInFunction(Function, BuiltInFunctionType):
|
||||||
-------
|
-------
|
||||||
The FunctionSpec of the BuiltInFunction
|
The FunctionSpec of the BuiltInFunction
|
||||||
"""
|
"""
|
||||||
return FunctionSpec.from_callable(self.callable)
|
return FunctionSpec.from_callable(name=self.name, callable_ref=self.callable)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _arg_output_type(cls, arg: Argument) -> Type[Argument]:
|
def _arg_output_type(cls, arg: Argument) -> Type[Argument]:
|
||||||
|
|
@ -257,14 +257,27 @@ class BuiltInFunction(Function, BuiltInFunctionType):
|
||||||
resolved_variables: Dict[Variable, Resolvable],
|
resolved_variables: Dict[Variable, Resolvable],
|
||||||
custom_functions: Dict[str, "VariableDependency"],
|
custom_functions: Dict[str, "VariableDependency"],
|
||||||
) -> Resolvable:
|
) -> Resolvable:
|
||||||
|
# TODO: Make conditionals not execute all branches!!!
|
||||||
|
conditional_return_args = self.function_spec.conditional_arg_indices(
|
||||||
|
num_input_args=len(self.args)
|
||||||
|
)
|
||||||
|
|
||||||
# Resolve all non-lambda arguments
|
# Resolve all non-lambda arguments
|
||||||
resolved_arguments: List[Resolvable | Lambda] = [
|
resolved_arguments: List[Resolvable | Lambda | ReturnableArgument] = [
|
||||||
|
(
|
||||||
self._resolve_argument_type(
|
self._resolve_argument_type(
|
||||||
arg=arg,
|
arg=arg,
|
||||||
resolved_variables=resolved_variables,
|
resolved_variables=resolved_variables,
|
||||||
custom_functions=custom_functions,
|
custom_functions=custom_functions,
|
||||||
)
|
)
|
||||||
for arg in self.args
|
if idx not in conditional_return_args
|
||||||
|
else ReturnableArgument(
|
||||||
|
value=functools.partial(
|
||||||
|
self._resolve_argument_type, arg, resolved_variables, custom_functions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for idx, arg in enumerate(self.args)
|
||||||
]
|
]
|
||||||
|
|
||||||
# If a lambda is in a function's arg, resolve it differently
|
# If a lambda is in a function's arg, resolve it differently
|
||||||
|
|
@ -283,8 +296,6 @@ class BuiltInFunction(Function, BuiltInFunctionType):
|
||||||
custom_functions=custom_functions,
|
custom_functions=custom_functions,
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Make conditionals not execute all branches!!!
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.callable(*resolved_arguments)
|
return self.callable(*resolved_arguments)
|
||||||
except (UserThrownRuntimeError, RuntimeException):
|
except (UserThrownRuntimeError, RuntimeException):
|
||||||
|
|
|
||||||
|
|
@ -34,17 +34,21 @@ class VariableDependency(ABC):
|
||||||
Any arguments in the VariableDependency that may or may not need to be resolved.
|
Any arguments in the VariableDependency that may or may not need to be resolved.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _recurse_get(self, ttype: Type[TypeT], subclass: bool = False) -> List[TypeT]:
|
def _recurse_get(
|
||||||
|
self, ttype: Type[TypeT], subclass: bool = False, instance: bool = True
|
||||||
|
) -> List[TypeT]:
|
||||||
output: List[TypeT] = []
|
output: List[TypeT] = []
|
||||||
for arg in self._iterable_arguments:
|
for arg in self._iterable_arguments:
|
||||||
if subclass and issubclass(type(arg), ttype):
|
if subclass and issubclass(type(arg), ttype):
|
||||||
output.append(arg)
|
output.append(arg)
|
||||||
elif isinstance(arg, ttype):
|
elif instance and isinstance(arg, ttype):
|
||||||
|
output.append(arg)
|
||||||
|
elif type(arg) == ttype:
|
||||||
output.append(arg)
|
output.append(arg)
|
||||||
|
|
||||||
if isinstance(arg, VariableDependency):
|
if isinstance(arg, VariableDependency):
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
output.extend(arg._recurse_get(ttype))
|
output.extend(arg._recurse_get(ttype, subclass=subclass, instance=instance))
|
||||||
# pylint: enable=protected-access
|
# pylint: enable=protected-access
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
@ -57,7 +61,7 @@ class VariableDependency(ABC):
|
||||||
-------
|
-------
|
||||||
All Variables that this depends on.
|
All Variables that this depends on.
|
||||||
"""
|
"""
|
||||||
return set(self._recurse_get(Variable))
|
return set(self._recurse_get(Variable, instance=False))
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ def is_type_compatible(
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FunctionSpec:
|
class FunctionSpec:
|
||||||
|
function_name: str
|
||||||
return_type: Type[Resolvable]
|
return_type: Type[Resolvable]
|
||||||
arg_names: List[str]
|
arg_names: List[str]
|
||||||
args: Optional[List[Type[Resolvable | Optional[Resolvable]]]] = None
|
args: Optional[List[Type[Resolvable | Optional[Resolvable]]]] = None
|
||||||
|
|
@ -185,6 +186,22 @@ class FunctionSpec:
|
||||||
return sum(1 for arg in self.args if not is_optional(arg))
|
return sum(1 for arg in self.args if not is_optional(arg))
|
||||||
return 0 # varargs can take any number
|
return 0 # varargs can take any number
|
||||||
|
|
||||||
|
def conditional_arg_indices(self, num_input_args: int) -> List[int]:
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
If the function is conditional, return the indices of the arguments that
|
||||||
|
return for different branches.
|
||||||
|
"""
|
||||||
|
if self.function_name == "if":
|
||||||
|
return [1, 2] # true, false
|
||||||
|
if self.function_name == "elif":
|
||||||
|
# if, retA, elif, retB, retElse
|
||||||
|
return list(range(1, num_input_args, 2)) + [num_input_args - 1]
|
||||||
|
if self.function_name == "if_passthrough":
|
||||||
|
return [0, 1] # true-passthrough, false-passthrough
|
||||||
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_lambda_reduce_function(self) -> Optional[Type[LambdaReduce]]:
|
def is_lambda_reduce_function(self) -> Optional[Type[LambdaReduce]]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -259,7 +276,7 @@ class FunctionSpec:
|
||||||
return self._to_human_readable_name(self.return_type)
|
return self._to_human_readable_name(self.return_type)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_callable(cls, callable_ref: Callable[..., Resolvable]) -> "FunctionSpec":
|
def from_callable(cls, name: str, callable_ref: Callable[..., Resolvable]) -> "FunctionSpec":
|
||||||
"""
|
"""
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
@ -268,12 +285,14 @@ class FunctionSpec:
|
||||||
arg_spec: FullArgSpec = inspect.getfullargspec(callable_ref)
|
arg_spec: FullArgSpec = inspect.getfullargspec(callable_ref)
|
||||||
if arg_spec.varargs:
|
if arg_spec.varargs:
|
||||||
return FunctionSpec(
|
return FunctionSpec(
|
||||||
|
function_name=name,
|
||||||
return_type=arg_spec.annotations["return"],
|
return_type=arg_spec.annotations["return"],
|
||||||
arg_names=[arg_spec.varargs],
|
arg_names=[arg_spec.varargs],
|
||||||
varargs=arg_spec.annotations[arg_spec.varargs],
|
varargs=arg_spec.annotations[arg_spec.varargs],
|
||||||
)
|
)
|
||||||
|
|
||||||
return FunctionSpec(
|
return FunctionSpec(
|
||||||
|
function_name=name,
|
||||||
return_type=arg_spec.annotations["return"],
|
return_type=arg_spec.annotations["return"],
|
||||||
arg_names=arg_spec.args,
|
arg_names=arg_spec.args,
|
||||||
args=[arg_spec.annotations[arg_name] for arg_name in arg_spec.args],
|
args=[arg_spec.annotations[arg_name] for arg_name in arg_spec.args],
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,23 @@ class TestLambdaFunction:
|
||||||
}
|
}
|
||||||
).resolve() == ScriptOutput({"output": Integer(4)})
|
).resolve() == ScriptOutput({"output": Integer(4)})
|
||||||
|
|
||||||
|
def test_multiple_lambdas_single_definition(self):
|
||||||
|
url_map_def = """{
|
||||||
|
%array_reduce(
|
||||||
|
%array_apply( array_def, %array_map_format),
|
||||||
|
%map_extend
|
||||||
|
)
|
||||||
|
}"""
|
||||||
|
script = Script(
|
||||||
|
{
|
||||||
|
"%array_map_format": "{ {$0: $0 } }",
|
||||||
|
"array_def": "{ [1, 2, 3] }",
|
||||||
|
"category_url_map": url_map_def,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert script.resolve().get("category_url_map").native == {1: 1, 2: 2, 3: 3}
|
||||||
|
|
||||||
|
|
||||||
class TestLambdaFunctionIncompatibleNumArguments:
|
class TestLambdaFunctionIncompatibleNumArguments:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ def function_class_to_name(obj: Type[Any]) -> str:
|
||||||
|
|
||||||
|
|
||||||
def function_type_hinting(display_function_name: str, function: Any) -> str:
|
def function_type_hinting(display_function_name: str, function: Any) -> str:
|
||||||
spec = FunctionSpec.from_callable(function)
|
spec = FunctionSpec.from_callable(name=display_function_name, callable_ref=function)
|
||||||
out = ":spec: ``"
|
out = ":spec: ``"
|
||||||
out += display_function_name
|
out += display_function_name
|
||||||
out += spec.human_readable_input_args()
|
out += spec.human_readable_input_args()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue