slick script class
This commit is contained in:
parent
0477d29f44
commit
0735d8a8ac
4 changed files with 74 additions and 21 deletions
|
|
@ -11,6 +11,7 @@ from ytdl_sub.script.types.variable import Variable
|
||||||
from ytdl_sub.script.utils.exceptions import UNREACHABLE
|
from ytdl_sub.script.utils.exceptions import UNREACHABLE
|
||||||
from ytdl_sub.script.utils.exceptions import CycleDetected
|
from ytdl_sub.script.utils.exceptions import CycleDetected
|
||||||
from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArguments
|
from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArguments
|
||||||
|
from ytdl_sub.script.utils.exceptions import RuntimeException
|
||||||
from ytdl_sub.script.utils.name_validation import validate_variable_name
|
from ytdl_sub.script.utils.name_validation import validate_variable_name
|
||||||
|
|
||||||
# pylint: disable=missing-raises-doc
|
# pylint: disable=missing-raises-doc
|
||||||
|
|
@ -143,6 +144,12 @@ class Script:
|
||||||
f"{nested_custom_function.num_input_args}"
|
f"{nested_custom_function.num_input_args}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _validate(self) -> None:
|
||||||
|
self._ensure_no_custom_function_cycles()
|
||||||
|
self._ensure_custom_function_arguments_valid()
|
||||||
|
self._ensure_no_variable_cycles()
|
||||||
|
self._ensure_custom_function_usage_num_input_arguments_valid()
|
||||||
|
|
||||||
def __init__(self, script: Dict[str, str]):
|
def __init__(self, script: Dict[str, str]):
|
||||||
function_names: Set[str] = {
|
function_names: Set[str] = {
|
||||||
self._function_name(name) for name in script.keys() if self._is_function(name)
|
self._function_name(name) for name in script.keys() if self._is_function(name)
|
||||||
|
|
@ -174,25 +181,29 @@ class Script:
|
||||||
for variable_key, variable_value in script.items()
|
for variable_key, variable_value in script.items()
|
||||||
if not self._is_function(variable_key)
|
if not self._is_function(variable_key)
|
||||||
}
|
}
|
||||||
|
self._validate()
|
||||||
|
|
||||||
self._ensure_no_custom_function_cycles()
|
def _update_internally(self, resolved_variables: Dict[str, Resolvable]) -> None:
|
||||||
self._ensure_custom_function_arguments_valid()
|
for variable_name, resolved in resolved_variables.items():
|
||||||
self._ensure_no_variable_cycles()
|
self._variables[variable_name] = SyntaxTree(ast=[resolved])
|
||||||
self._ensure_custom_function_usage_num_input_arguments_valid()
|
|
||||||
|
|
||||||
def resolve(
|
def resolve(
|
||||||
self,
|
self,
|
||||||
resolved: Optional[Dict[str, Resolvable]] = None,
|
resolved: Optional[Dict[str, Resolvable]] = None,
|
||||||
unresolvable: Optional[Set[str]] = None,
|
unresolvable: Optional[Set[str]] = None,
|
||||||
|
update: bool = False,
|
||||||
) -> Dict[str, Resolvable]:
|
) -> Dict[str, Resolvable]:
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
resolved
|
resolved
|
||||||
Optional variables that have been resolved elsewhere and could be used in this script
|
Optional. Variables that have been resolved elsewhere and could be used in this script
|
||||||
unresolvable
|
unresolvable
|
||||||
Variables that cannot be resolved, forcing any variable that depends on it to not be
|
Optional. Variables that cannot be resolved, forcing any variable that depends on it
|
||||||
resolved.
|
to not be resolved.
|
||||||
|
update
|
||||||
|
Optional. Whether to update the internal representation of variables with their
|
||||||
|
resolved value (if they get resolved).
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
@ -232,4 +243,32 @@ class Script:
|
||||||
# since cycles are detected in __init__
|
# since cycles are detected in __init__
|
||||||
raise UNREACHABLE
|
raise UNREACHABLE
|
||||||
|
|
||||||
return {variable.name: resolvable for variable, resolvable in resolved.items()}
|
resolved_variables = {
|
||||||
|
variable.name: resolvable for variable, resolvable in resolved.items()
|
||||||
|
}
|
||||||
|
if update:
|
||||||
|
self._update_internally(resolved_variables=resolved_variables)
|
||||||
|
|
||||||
|
return resolved_variables
|
||||||
|
|
||||||
|
def add(self, variables: Dict[str, str]) -> "Script":
|
||||||
|
for variable_name, variable_definition in variables.items():
|
||||||
|
self._variables[variable_name] = parse(
|
||||||
|
text=variable_definition,
|
||||||
|
name=variable_name,
|
||||||
|
custom_function_names=set(self._functions.keys()),
|
||||||
|
variable_names=set(self._variables.keys()).union(variables.keys()),
|
||||||
|
)
|
||||||
|
self._validate()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get(self, variable_name: str) -> Resolvable:
|
||||||
|
if variable_name not in self._variables:
|
||||||
|
raise RuntimeException(
|
||||||
|
f"Tried to get resolved variable {variable_name}, but it does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (resolvable := self._variables[variable_name].resolvable) is not None:
|
||||||
|
return resolvable
|
||||||
|
|
||||||
|
raise RuntimeException(f"Tried to get unresolved variable {variable_name}")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from ytdl_sub.script.types.resolvable import Argument
|
from ytdl_sub.script.types.resolvable import Argument
|
||||||
from ytdl_sub.script.types.resolvable import Resolvable
|
from ytdl_sub.script.types.resolvable import Resolvable
|
||||||
|
|
@ -38,3 +39,9 @@ class SyntaxTree(VariableDependency):
|
||||||
|
|
||||||
# Otherwise, to concat multiple resolved outputs, we must concat as strings
|
# Otherwise, to concat multiple resolved outputs, we must concat as strings
|
||||||
return String("".join([str(res) for res in resolved]))
|
return String("".join([str(res) for res in resolved]))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def resolvable(self) -> Optional[Resolvable]:
|
||||||
|
if len(self.ast) == 1 and isinstance(self.ast[0], Resolvable):
|
||||||
|
return self.ast[0]
|
||||||
|
return None
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ class UserException(ValidationException, ABC):
|
||||||
"""It's the user's fault!"""
|
"""It's the user's fault!"""
|
||||||
|
|
||||||
|
|
||||||
|
class RuntimeException(ValueError, ABC):
|
||||||
|
"""Exception thrown at runtime during resolution"""
|
||||||
|
|
||||||
|
|
||||||
class InvalidSyntaxException(UserException):
|
class InvalidSyntaxException(UserException):
|
||||||
"""Syntax is incorrect"""
|
"""Syntax is incorrect"""
|
||||||
|
|
||||||
|
|
@ -43,10 +47,6 @@ class CycleDetected(UserException):
|
||||||
"""A cycle exists within a user's script"""
|
"""A cycle exists within a user's script"""
|
||||||
|
|
||||||
|
|
||||||
class RuntimeException(ValueError, ABC):
|
|
||||||
"""Exception thrown at runtime during resolution"""
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionRuntimeException(RuntimeException):
|
class FunctionRuntimeException(RuntimeException):
|
||||||
"""Exception thrown when a ytdl-sub function has an error occur at runtime"""
|
"""Exception thrown when a ytdl-sub function has an error occur at runtime"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,16 +34,23 @@ class TestScript:
|
||||||
"entry": "{%throw('entry has not been populated yet')}",
|
"entry": "{%throw('entry has not been populated yet')}",
|
||||||
"title": "{%map_get(entry, 'title')}",
|
"title": "{%map_get(entry, 'title')}",
|
||||||
"override": "hi",
|
"override": "hi",
|
||||||
|
"resolved_override": "{override} mom",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
overrides = script.resolve(unresolvable={"entry"})
|
script.resolve(unresolvable={"entry"}, update=True)
|
||||||
assert overrides == {"override": String("hi")}
|
assert script.get("override") == String("hi")
|
||||||
|
assert script.get("resolved_override") == String("hi mom")
|
||||||
|
|
||||||
entry_map = ResolvedMap({String("title"): String("the title")})
|
script.add(
|
||||||
entry_output = script.resolve(resolved=dict(overrides, **{"entry": entry_map}))
|
{
|
||||||
assert entry_output == {
|
"new_variable": "{resolved_override} {title}",
|
||||||
"override": String("hi"),
|
"new_variable_upper": "{%upper(new_variable)}",
|
||||||
"entry": entry_map,
|
|
||||||
"title": String("the title"),
|
|
||||||
}
|
}
|
||||||
|
).resolve(
|
||||||
|
resolved={"entry": ResolvedMap({String("title"): String("the title")})}, 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")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue