register function support

This commit is contained in:
Jesse Bannon 2023-12-04 23:21:32 -08:00
parent 1e58f9d946
commit e6ee337f20
3 changed files with 34 additions and 8 deletions

View file

@ -1,4 +1,5 @@
from typing import Callable from typing import Callable
from typing import Dict
from ytdl_sub.script.functions.array_functions import ArrayFunctions from ytdl_sub.script.functions.array_functions import ArrayFunctions
from ytdl_sub.script.functions.boolean_functions import BooleanFunctions from ytdl_sub.script.functions.boolean_functions import BooleanFunctions
@ -22,9 +23,11 @@ class Functions(
ErrorFunctions, ErrorFunctions,
RegexFunctions, RegexFunctions,
): ):
_custom_functions: Dict[str, Callable[..., Resolvable]] = {}
@classmethod @classmethod
def is_built_in(cls, name: str) -> bool: def is_built_in(cls, name: str) -> bool:
return hasattr(cls, name) or hasattr(cls, f"{name}_") return hasattr(cls, name) or hasattr(cls, f"{name}_") or name in cls._custom_functions
@classmethod @classmethod
def get(cls, name: str) -> Callable[..., Resolvable]: def get(cls, name: str) -> Callable[..., Resolvable]:
@ -32,5 +35,15 @@ class Functions(
return getattr(cls, name) return getattr(cls, name)
if hasattr(cls, f"{name}_"): if hasattr(cls, f"{name}_"):
return getattr(cls, f"{name}_") return getattr(cls, f"{name}_")
if name in cls._custom_functions:
return cls._custom_functions[name]
raise FunctionDoesNotExistRuntimeException(f"The function {name} does not exist") raise FunctionDoesNotExistRuntimeException(f"The function {name} does not exist")
@classmethod
def register_function(cls, function: Callable[..., Resolvable]) -> None:
if cls.is_built_in(function.__name__):
raise ValueError(
f"Cannot register a function with name {function.__name__} because it already exists"
)
cls._custom_functions[function.__name__] = function

View file

@ -91,13 +91,11 @@ class BuiltInFunction(Function, BuiltInFunctionType):
@property @property
def callable(self) -> Callable[..., Resolvable]: def callable(self) -> Callable[..., Resolvable]:
if hasattr(Functions, self.name): try:
return getattr(Functions, self.name) return Functions.get(self.name)
if hasattr(Functions, self.name + "_"): except Exception as exc:
return getattr(Functions, self.name + "_") # Should be validated in the parser
raise UNREACHABLE from exc
# Should be validated in the parser
raise UNREACHABLE
@functools.cached_property @functools.cached_property
def function_spec(self) -> FunctionSpec: def function_spec(self) -> FunctionSpec:

View file

@ -1,9 +1,12 @@
import re import re
import pytest import pytest
from unit.script.conftest import single_variable_output
from ytdl_sub.script.functions import Functions
from ytdl_sub.script.parser import FUNCTION_INVALID_CHAR from ytdl_sub.script.parser import FUNCTION_INVALID_CHAR
from ytdl_sub.script.script import Script from ytdl_sub.script.script import Script
from ytdl_sub.script.types.resolvable import Integer
from ytdl_sub.script.utils.exceptions import FunctionDoesNotExist from ytdl_sub.script.utils.exceptions import FunctionDoesNotExist
from ytdl_sub.script.utils.exceptions import FunctionRuntimeException from ytdl_sub.script.utils.exceptions import FunctionRuntimeException
from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments
@ -14,6 +17,10 @@ def _incompatible_arguments_match(expected: str, recieved: str) -> str:
return re.escape(f"Expected ({expected})\nReceived ({recieved})") return re.escape(f"Expected ({expected})\nReceived ({recieved})")
def mock_register_function(integer: Integer) -> Integer:
return Integer(integer.value + 100)
class TestFunction: class TestFunction:
def test_nested_if_function_incompatible(self): def test_nested_if_function_incompatible(self):
function_str = """{ function_str = """{
@ -71,3 +78,11 @@ class TestFunction:
match=re.escape(str(FUNCTION_INVALID_CHAR)), match=re.escape(str(FUNCTION_INVALID_CHAR)),
): ):
Script({"dne": "{%throw}"}).resolve() Script({"dne": "{%throw}"}).resolve()
def test_register_function(self):
try:
Functions.register_function(function=mock_register_function)
output = single_variable_output(f"{{%mock_register_function(10)}}")
assert output == 110
finally:
del Functions._custom_functions[mock_register_function.__name__]