diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index 38e0346e..3b54c9c1 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -695,3 +695,52 @@ class Script: Names of all functions within the Script. """ return set(to_function_definition_name(name) for name in self._functions.keys()) + + def resolve_partial( + self, + unresolvable: Optional[Set[str]] = None, + ) -> "Script": + """ + + Returns + ------- + Dict of resolved values + + Raises + ------ + ScriptVariableNotResolved + If specifying a filter of variable to resolve, and one of them does not. + """ + resolved: Dict[Variable, Resolvable] = {} + unresolved: Dict[Variable, SyntaxTree] = { + Variable(name): ast for name, ast in self._variables.items() if name not in unresolvable + } + unresolvable: Set[Variable] = {Variable(name) for name in (unresolvable or {})} + + partially_resolved = True + while partially_resolved: + + partially_resolved = False + + for variable in list(unresolved.keys()): + + definition = unresolved[variable] + + maybe_resolved = definition.partial_resolve( + resolved_variables=resolved, custom_functions=self._functions + ) + if isinstance(maybe_resolved, Resolvable): + resolved[variable] = maybe_resolved + del unresolved[variable] + else: + unresolved[variable] = maybe_resolved + + # If the definition changed, then the script changed + # which means we can iterate again + partially_resolved |= definition != maybe_resolved + + return Script({}).add_parsed( + self._functions + | {var.name: definition for var, definition in resolved.items()} + | unresolved + ) diff --git a/src/ytdl_sub/script/types/array.py b/src/ytdl_sub/script/types/array.py index 26d99d40..56601e0b 100644 --- a/src/ytdl_sub/script/types/array.py +++ b/src/ytdl_sub/script/types/array.py @@ -49,10 +49,10 @@ class UnresolvedArray(_Array, VariableDependency, FutureResolvable): ) def partial_resolve( - self, + self: TypeT, resolved_variables: Dict[Variable, Resolvable], custom_functions: Dict[str, "VariableDependency"], - ) -> "UnresolvedArray" | Resolvable: + ) -> TypeT | Resolvable: maybe_resolvable_values, is_resolvable = VariableDependency.try_partial_resolve( args=self.value, resolved_variables=resolved_variables, diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index 5ff6111a..593e6a8e 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -45,7 +45,7 @@ class Function(FunctionType, VariableDependency, ABC): custom_functions: Dict[str, "VariableDependency"], ) -> TypeT | Resolvable: maybe_resolvable_values, is_resolvable = VariableDependency.try_partial_resolve( - args=self.value, + args=self.args, resolved_variables=resolved_variables, custom_functions=custom_functions, ) diff --git a/src/ytdl_sub/script/types/map.py b/src/ytdl_sub/script/types/map.py index 4344e45c..841d491c 100644 --- a/src/ytdl_sub/script/types/map.py +++ b/src/ytdl_sub/script/types/map.py @@ -13,6 +13,7 @@ from ytdl_sub.script.types.resolvable import NonHashable from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import ResolvableToJson from ytdl_sub.script.types.variable import Variable +from ytdl_sub.script.types.variable_dependency import TypeT from ytdl_sub.script.types.variable_dependency import VariableDependency from ytdl_sub.script.utils.exceptions import KeyNotHashableRuntimeException @@ -56,10 +57,10 @@ class UnresolvedMap(_Map, VariableDependency, FutureResolvable): return Map(output) def partial_resolve( - self, + self: TypeT, resolved_variables: Dict[Variable, Resolvable], custom_functions: Dict[str, "VariableDependency"], - ) -> "UnresolvedMap" | Resolvable: + ) -> TypeT | Resolvable: maybe_resolvable_keys, is_keys_resolvable = VariableDependency.try_partial_resolve( args=self.value.keys(), resolved_variables=resolved_variables, diff --git a/src/ytdl_sub/script/types/syntax_tree.py b/src/ytdl_sub/script/types/syntax_tree.py index eff39990..cc383f22 100644 --- a/src/ytdl_sub/script/types/syntax_tree.py +++ b/src/ytdl_sub/script/types/syntax_tree.py @@ -7,6 +7,7 @@ from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import String from ytdl_sub.script.types.variable import Variable +from ytdl_sub.script.types.variable_dependency import TypeT from ytdl_sub.script.types.variable_dependency import VariableDependency @@ -40,6 +41,25 @@ class SyntaxTree(VariableDependency): # Otherwise, to concat multiple resolved outputs, we must concat as strings return String("".join([str(res) for res in resolved])) + def partial_resolve( + self: TypeT, + resolved_variables: Dict[Variable, Resolvable], + custom_functions: Dict[str, VariableDependency], + ) -> TypeT | Resolvable: + maybe_resolvable_values, is_resolvable = VariableDependency.try_partial_resolve( + args=self.ast, + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + if is_resolvable: + return self.resolve( + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + return SyntaxTree(ast=maybe_resolvable_values) + @property def maybe_resolvable(self) -> Optional[Resolvable]: """ @@ -76,3 +96,10 @@ class ResolvedSyntaxTree(SyntaxTree): @property def maybe_resolvable(self) -> Optional[Resolvable]: return self.ast[0] + + def partial_resolve( + self: TypeT, + resolved_variables: Dict[Variable, Resolvable], + custom_functions: Dict[str, VariableDependency], + ) -> TypeT | Resolvable: + return self.ast[0] diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index 4ec4b905..9425524a 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -271,7 +271,14 @@ class VariableDependency(ABC): if not isinstance(maybe_resolvable_args[-1], Resolvable): is_resolvable = False + elif isinstance(arg, Variable): + if arg not in resolved_variables: + is_resolvable = False + + maybe_resolvable_args.append(arg) else: maybe_resolvable_args.append(arg) + if is_resolvable: + print("hmm") return maybe_resolvable_args, is_resolvable diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index c48f4f0e..418e1d31 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -598,3 +598,22 @@ def test_default_docker_config_and_subscriptions( "year": "{episode_year}", }, } + + +def test_default_docker_config_and_subscriptions( + docker_default_subscription_path: Path, output_directory: str +): + default_config = ConfigFile.from_file_path("docker/root/defaults/config.yaml") + default_subs = Subscription.from_file_path( + config=default_config, subscription_path=docker_default_subscription_path + ) + assert len(default_subs) == 1 + + unresolvable = default_subs[0].plugins.get_all_variables( + additional_options=[default_subs[0].downloader_options, default_subs[0].output_options] + ) + unresolvable.add("entry_metadata") + unresolvable.add("sibling_metadata") + + out = default_subs[0].overrides.script.resolve_partial(unresolvable=unresolvable) + print("hi")