|
@@ -18,11 +18,13 @@ import io
|
|
|
import re
|
|
|
import sys
|
|
|
import typing as t
|
|
|
+import uuid
|
|
|
from abc import ABC, abstractmethod
|
|
|
from collections.abc import Iterable
|
|
|
from types import FrameType, FunctionType
|
|
|
|
|
|
from .._warnings import _warn
|
|
|
+from ..utils import _getscopeattr
|
|
|
|
|
|
if sys.version_info < (3, 9):
|
|
|
from ..utils.unparse import _Unparser
|
|
@@ -50,8 +52,8 @@ class _Element(ABC):
|
|
|
return obj
|
|
|
|
|
|
def __init__(self, *args, **kwargs) -> None:
|
|
|
- self.__variables: t.Dict[str, FunctionType] = {}
|
|
|
self._properties: t.Dict[str, t.Any] = {}
|
|
|
+ self._lambdas: t.Dict[str, str] = {}
|
|
|
self.__calling_frame = t.cast(
|
|
|
FrameType, t.cast(FrameType, t.cast(FrameType, inspect.currentframe()).f_back).f_back
|
|
|
)
|
|
@@ -65,6 +67,11 @@ class _Element(ABC):
|
|
|
self._properties.update(kwargs)
|
|
|
self.parse_properties()
|
|
|
|
|
|
+ def _evaluate_lambdas(self, gui: Gui):
|
|
|
+ for k, lmbd in self._lambdas.items():
|
|
|
+ expr = gui._evaluate_expr(f"{{{lmbd}}}")
|
|
|
+ gui._bind_var_val(k, _getscopeattr(gui, expr))
|
|
|
+
|
|
|
# Convert property value to string/function
|
|
|
def parse_properties(self):
|
|
|
self._properties = {
|
|
@@ -96,9 +103,7 @@ class _Element(ABC):
|
|
|
_LambdaByName(self._ELEMENT_NAME, lambda_by_name).visit(st)
|
|
|
lambda_fn = lambda_by_name.get(
|
|
|
key,
|
|
|
- lambda_by_name.get(_LambdaByName._DEFAULT_NAME, None)
|
|
|
- if key == self._DEFAULT_PROPERTY
|
|
|
- else None,
|
|
|
+ lambda_by_name.get(_LambdaByName._DEFAULT_NAME, None) if key == self._DEFAULT_PROPERTY else None,
|
|
|
)
|
|
|
if lambda_fn is not None:
|
|
|
args = [arg.arg for arg in lambda_fn.args.args]
|
|
@@ -119,22 +124,15 @@ class _Element(ABC):
|
|
|
lambda_text = string_fd.read()
|
|
|
else:
|
|
|
lambda_text = ast.unparse(tree)
|
|
|
- new_code = compile(f"{_Element._NEW_LAMBDA_NAME} = {lambda_text}", "<ast>", "exec")
|
|
|
- namespace: t.Dict[str, FunctionType] = {}
|
|
|
- exec(new_code, namespace)
|
|
|
- var_name = f"__lambda_{id(namespace[_Element._NEW_LAMBDA_NAME])}"
|
|
|
- self.__variables[var_name] = namespace[_Element._NEW_LAMBDA_NAME]
|
|
|
- return f'{{{var_name}({", ".join(args)})}}'
|
|
|
+ lambda_name = f"__lambda_{uuid.uuid4().hex}"
|
|
|
+ self._lambdas[lambda_name] = lambda_text
|
|
|
+ return f'{{{lambda_name}({", ".join(args)})}}'
|
|
|
except Exception as e:
|
|
|
_warn("Error in lambda expression", e)
|
|
|
if hasattr(value, "__name__"):
|
|
|
return str(getattr(value, "__name__")) # noqa: B009
|
|
|
return str(value)
|
|
|
|
|
|
- def _bind_variables(self, gui: "Gui"):
|
|
|
- for var_name, var_value in self.__variables.items():
|
|
|
- gui._bind_var_val(var_name, var_value)
|
|
|
-
|
|
|
@abstractmethod
|
|
|
def _render(self, gui: "Gui") -> str:
|
|
|
pass
|
|
@@ -161,7 +159,7 @@ class _Block(_Element):
|
|
|
_BuilderContextManager().pop()
|
|
|
|
|
|
def _render(self, gui: "Gui") -> str:
|
|
|
- self._bind_variables(gui)
|
|
|
+ self._evaluate_lambdas(gui)
|
|
|
el = _BuilderFactory.create_element(gui, self._ELEMENT_NAME, self._deepcopy_properties())
|
|
|
return f"{el[0]}{self._render_children(gui)}</{el[1]}>"
|
|
|
|
|
@@ -172,7 +170,7 @@ class _Block(_Element):
|
|
|
class _DefaultBlock(_Block):
|
|
|
_ELEMENT_NAME = "part"
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
+ def __init__(self, *args, **kwargs): # do not remove as it could break the search in frames
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
@@ -225,7 +223,7 @@ class html(_Block):
|
|
|
self._content = args[1] if len(args) > 1 else ""
|
|
|
|
|
|
def _render(self, gui: "Gui") -> str:
|
|
|
- self._bind_variables(gui)
|
|
|
+ self._evaluate_lambdas(gui)
|
|
|
if self._ELEMENT_NAME:
|
|
|
attrs = ""
|
|
|
if self._properties:
|
|
@@ -238,11 +236,11 @@ class html(_Block):
|
|
|
class _Control(_Element):
|
|
|
"""NOT DOCUMENTED"""
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
+ def __init__(self, *args, **kwargs): # do not remove as it could break the search in frames
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
def _render(self, gui: "Gui") -> str:
|
|
|
- self._bind_variables(gui)
|
|
|
+ self._evaluate_lambdas(gui)
|
|
|
el = _BuilderFactory.create_element(gui, self._ELEMENT_NAME, self._deepcopy_properties())
|
|
|
return (
|
|
|
f"<div>{el[0]}</{el[1]}></div>"
|