register function support
This commit is contained in:
parent
1e58f9d946
commit
e6ee337f20
3 changed files with 34 additions and 8 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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__]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue